aussi appelées dunder methods
méthodes spéciales / dunder methods¶
sur une classe on peut définir des méthodes spéciales
pour bien intégrer les objets dans le langage
c’est-à-dire donner du sens à des constructions du langage
c’est-à-dire donner un sens à des phrases commme:
appeler les fonctions builtin:
len(obj),int(obj)opérateurs: e.g.
obj + xitération:
for item in objtest d’appartenance:
x in objindexation:
obj[x]et même appel!
obj(x)etc...
len(obj)¶
class Classe:
def __init__(self, students):
self.students = students
def __len__(self):
return len(self.students)The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
classe = Classe(['jean', 'laurent', 'benoit'])
len(classe)3de manière similaire :
__int__(self)pour redéfinirint(obj)et similaires
opérateurs: obj1 + obj2¶
class Classe:
def __init__(self, students):
self.students = students
def __add__(self, other):
return Classe(self.students + other.students)
def __repr__(self):
return f"[{len(self.students)} students]"classe1 = Classe(['marie', 'claire'])
classe2 = Classe(['jean', 'laurent'])
classe1 + classe2[4 students]en réalité c’est un peu plus subtil
dans la pratique, on peut aussi avoir à définir __radd__ de façon à redéfinir le cas où on pourrait s’additionner avec des objets d’un autre type, comme des types builtin de nombres par exemple; mais ne nous égarons pas..
itérations: for item in obj:¶
class Classe:
def __init__(self, students):
self.students = students
def __iter__(self):
"""
iterate on self as if it was self.students
"""
return iter(self.students)classe = Classe(['jean', 'laurent', 'benoit'])
for s in classe:
print(s)jean
laurent
benoit
# et même d'ailleurs
x, y, z = classe
y'laurent'appartenance: x in obj¶
on l’a vu déjà avec la classe Circle:
class Classe:
def __init__(self, students):
self.students = students
def __contains__(self, student):
return student in self.studentsclasse = Classe(['jean', 'laurent', 'benoit'])
'jean' in classeTrueindexations: obj[x]¶
class Classe:
def __init__(self, students):
self.students = students
def __getitem__(self, index):
if isinstance(index, int):
return self.students[index]
elif isinstance(index, str):
if index in self.students:
return index
else:
return Noneclasse = Classe(['jean', 'laurent', 'benoit'])
classe[1]'laurent'classe['jean']'jean'classe['pierre'] is NoneTrueclasse callable: obj(x)¶
on peut même donner du sens à obj(x)
# make it callable
class Line:
"""
the line of equation y = ax + b
"""
def __init__(self, a, b):
self.a = a
self.b = b
def __call__(self, x):
"""
can be used as function that
computes y = ax+b given x
"""
return self.a * x + self.b# cet objet se comporte comme une fonction
line = Line(2, 2)
# du coup c'est intéressant de pouvoir l'appeler
# comme si c'était réellement une fonction
line(1)4classe sortable: obj < obj2¶
pour ne pas changer d’exemple, imaginons que l’on veuille pouvoir trier une collection d’instances de Line
pour cela il suffit d’expliciter l’ordre entre les instances
et pour cela une technique consiste à redéfinir la dunder __lt__ (pour lower than)
ça se présenterait comme suit
# make it sortable
class Line:
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f"{self.a}x + {self.b}"
# on définit l'ordre entre éléments, ici: self < other
def __lt__(self, other):
return (self.a, self.b) < (other.a, other.b)
lines = [ Line(3, 1), Line(1, 2), Line(3, -1), Line(2, 0)]# du coup la classe sait comparer deux éléments entre eux
# prenons par exemple les deux premiers éléments
L1, L2, *_ = lines
# et comparons-les
L1 < L2False# du coup on peut trier ces éléments sans avoir à préciser la fonction de tri
sorted(lines)[1x + 2, 2x + 0, 3x + -1, 3x + 1]1 sur 6
pour notre exemple simple cela suffit bien, mais en toute rigueur pour pouvoir comparer deux objets il faudrait définir 6 opérateurs (<, <=, >, >=, == et !=); pour davantage de détails voyez notamment cette page
https://
classe hashable: D[obj]¶
on a pu dire que les clés des dictionnaires devaient être des objets non mutables; c’est vrai pour les types de base du langage
par contre lorsqu’il s’agit de classes user-defined, cette contrainte est levée si la classe implémente le protocole dit des objets hashables
toujours avec notre exemple de la classe Line: on pourrait avoir envie de créer des ensembles d’objets de type Line; ou bien encore d’utiliser un objet Line comme un clé de dictionnaire
cela est possible par exemple comme ceci: la classe doit implémenter deux dunder méthodes __eq__ et __hash__
# make it hashable
class Line:
def __init__(self, a, b):
self.a = a
self.b = b
# pour définir à quelle condition deux objets
# sont considérés égaux - au sens de ==
def __eq__(self, other):
return (self.a == other.a) and (self.f == other.b)
# comment doit-on calculer le hash d'un objet Line ?
def __hash__(self):
# on n'a qu'à le hasher comme le tuple (a, b)
return hash( (self.a, self.b) )# on peut alors utiliser les objets dans un dict ou dans un set !
D, S = {}, set()
line = Line(0, 0)
D[line] = "Yes !"
S = {line, line}
print(f"{D=}, {len(S)=}")D={<__main__.Line object at 0x7fb1adb67c80>: 'Yes !'}, len(S)=1
résumé¶
une classe peut définir des méthodes spéciales
notamment le constructeur pour l’initialisation,
souvent un afficheur pour
print()optionnellement d’autres pour donner du sens à des constructions du langage sur ces objets
ces méthodes ont toutes un nom en
__truc__(dunder methods)
pour en savoir plus¶
la (longue) liste exhaustive des méthodes spéciales est donnée dans la documentation officielle ici
https://