Licence CC BY-NC-ND, Thierry Parmentelat & Arnaud Legout
from IPython.display import HTML
HTML(filename="_static/style.html")
classes : méthodes spéciales#
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)
classe = Classe(['jean', 'laurent', 'benoit'])
len(classe)
3
de 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'
utiliser un générateur
lorsque la logique d’itération devient moins triviale que de simplement “sous-traiter” le travail à un autre objet, on utilise fréquemment un générateur pour implémenter la dunder __iter__
ici par exemple on aurait pu écrire
def __iter__(self):
for s in self.students:
yield s
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.students
classe = Classe(['jean', 'laurent', 'benoit'])
'jean' in classe
True
indexations: 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 None
classe = Classe(['jean', 'laurent', 'benoit'])
classe[1]
'laurent'
classe['jean']
'jean'
classe['pierre'] is None
True
classe 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)
4
classe 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 < L2
False
# 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]
trier, mais pas que
et pour le même prix on peut ainsi utiliser également tous les algorithmes qui reposent sur un ordre entre les éléments;
par exemple PriorityQueue/heapq pour les queues de priorité, ou encore bisect pour la recherche binaire, …
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://docs.python.org/3/library/functools.html#functools.total_ordering
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 0x7f4963f16db0>: '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://docs.python.org/3/reference/datamodel.html#special-method-names