Méthodes spéciales
Des méthodes à double underscore qui permettent aux objets de fonctionner avec les opérateurs ==, <, +, in et avec la fonction len().
Exemple
#class Score:
def __init__(self, student, points):
self.student = student
self.points = points
def __eq__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.points == other.points
def __lt__(self, other):
if not isinstance(other, Score):
return NotImplemented
return self.points < other.points
def __repr__(self):
return f"Score({self.student!r}, {self.points})"
a = Score("Léa", 92)
b = Score("Lucas", 78)
c = Score("Emma", 92)
print(a == c, a != b)
print(b < a, a > b)
print(sorted([a, b, c]))
print(max([a, b, c]))Définition et utilisation
#Les méthodes spéciales, aussi appelées méthodes magiques ou « dunder » (de double underscore), sont des méthodes dont le nom est entouré de doubles underscores. On ne les appelle généralement pas directement : c’est Python qui s’en charge quand vous utilisez un opérateur ou une fonction intégrée. L’écriture a == b se transforme en a.__eq__(b), a + b en a.__add__(b), et len(a) en a.__len__().
Grâce à elles, vos propres objets se comportent comme les types intégrés : des résultats peuvent être comparés et triés, des points additionnés avec l’opérateur +, et un cours traité comme une collection de leçons avec len() et in. Une telle surcharge d’opérateurs a du sens quand le sens de l’opération est évident. Additionner des points est clair ; additionner deux élèves, beaucoup moins.
Quand une méthode ne prend pas en charge le type du second argument, elle doit renvoyer la valeur spéciale NotImplemented, plutôt que de lever une exception. Python essaie alors l’opération inverse du côté du second objet (par ex. __radd__), et si celle-ci échoue aussi, il lève une TypeError claire.
Attention à l’égalité et au hashage : une classe qui définit __eq__ mais pas __hash__ ne peut plus être une clé de dictionnaire ni un élément d’ensemble. Le décorateur functools.total_ordering complète les comparaisons manquantes <=, > et >= à partir de __eq__ et __lt__.
Syntaxe
#class Nom:
def __eq__(self, other): ...
def __lt__(self, other): ...
def __add__(self, other): ...
def __len__(self): ...
def __contains__(self, item): ...Les méthodes spéciales les plus courantes
#__eq__()
a == b
L’égalité. Sans cette méthode, un objet n’est égal qu’à lui-même. L’opérateur!=renvoie automatiquement le résultat inverse.__lt__()
a < b
La comparaison « inférieur à ». Elle suffit pour trier avecsorted(), ainsi que pourmin()etmax(); Python traitea > bcommeb < a.__add__()
a + b
L’addition. Elle doit renvoyer un nouvel objet, etNotImplementedpour un type non pris en charge.__radd__()
b + a
L’addition quand l’objet se trouve à droite et que l’argument de gauche ne sait pas l’additionner. C’est grâce à elle quesum(), qui part de 0, fonctionne correctement.__len__()
len(a)
La longueur. Elle doit renvoyer un entier non négatif. Un objet de longueur 0 est faux dans une condition.__contains__()
x in a
Vérifie si un élément appartient à l’objet. Sans cette méthode,inparcourt les éléments, si l’objet est itérable.__hash__()
hash(a)
Le hash, nécessaire pour les ensembles et les clés de dictionnaire. Il doit se baser sur les mêmes champs que__eq__.
Plus d’exemples
#class Points:
def __init__(self, value):
self.value = value
def __add__(self, other):
if isinstance(other, Points):
return Points(self.value + other.value)
if isinstance(other, int):
return Points(self.value + other)
return NotImplemented
def __radd__(self, other):
return self + other
def __repr__(self):
return f"Points({self.value})"
print(Points(20) + Points(15))
print(5 + Points(20))
print(sum([Points(10), Points(25), Points(5)]))
try:
Points(10) + "beaucoup"
except TypeError as error:
print(error)class Course:
def __init__(self, title, lessons):
self.title = title
self.lessons = list(lessons)
def __len__(self):
return len(self.lessons)
def __contains__(self, topic):
return topic.lower() in (lesson.lower() for lesson in self.lessons)
python = Course("Python", ["Variables", "Boucles", "Fonctions"])
soon = Course("Bientôt", [])
print(len(python))
print("boucles" in python, "classes" in python)
print(bool(python), bool(soon))class Badge:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return isinstance(other, Badge) and self.name == other.name
def __hash__(self):
return hash(self.name)
earned = {Badge("Start"), Badge("Série 7"), Badge("Start")}
print(len(earned))
print(Badge("Série 7") in earned)Sans la méthode __hash__, la création de l’ensemble lèverait une TypeError, car une classe avec son propre __eq__ perd le hash par défaut.
Bonnes pratiques
#- Les méthodes de comparaison et de calcul renvoient
NotImplementedpour les types non pris en charge, plutôt que de lever une exception. Python essaie alors l’opération du côté du second objet. - Les opérateurs arithmétiques devraient créer un nouvel objet. Modifier
selfà l’intérieur de__add__surprendrait quiconque utilise+. - Vous obtenez le jeu complet de comparaisons avec le décorateur
functools.total_ordering, ou via@dataclass(order=True).
Entrées associées
#- __str__() et __repr__()Des méthodes qui définissent l’apparence textuelle d’un objet : lisible pour l’utilisateur (__str__) et technique pour le programmeur (__repr__).
- @dataclassUn décorateur qui génère __init__, __repr__ et __eq__ à partir des champs annotés d’une classe qui sert à stocker des données.
- len()Renvoie le nombre d’éléments d’une collection, ou le nombre de caractères d’une chaîne.
- sorted()Renvoie une nouvelle liste triée à partir des éléments de n’importe quelle collection.
Vous voyez une erreur ou il manque un exemple ? Écrivez-nous.