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().

Sur cette page

Exemple

#
Python
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]))
Résultatrésultat enregistré, vous pouvez le vérifier
True True
True True
[Score('Lucas', 78), Score('Léa', 92), Score('Emma', 92)]
Score('Léa', 92)

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

#
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 avec sorted(), ainsi que pour min() et max() ; Python traite a > b comme b < a.
  • __add__()

    a + b

    L’addition. Elle doit renvoyer un nouvel objet, et NotImplemented pour 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 que sum(), 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, in parcourt 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

#
Additionner des objets et sum()
Python
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)
Résultatrésultat enregistré, vous pouvez le vérifier
Points(35)
Points(25)
Points(40)
unsupported operand type(s) for +: 'Points' and 'str'
Une collection personnalisée : __len__ et __contains__
Python
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))
Résultatrésultat enregistré, vous pouvez le vérifier
3
True False
True False
Des objets dans un ensemble : __eq__ avec __hash__
Python
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)
Résultatrésultat enregistré, vous pouvez le vérifier
2
True

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 NotImplemented pour 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

#

Vous voyez une erreur ou il manque un exemple ? Écrivez-nous.