Les méthodes magiques en Python : donner un comportement natif à vos objets

Une méthode magique porte deux tirets bas de chaque côté : Python l'appelle à votre place dès que len, ==, un for ou un with croisent votre objet.
5 min de lecture
Believemy logo

Définition

Une classe que vous écrivez démarre plus pauvre que les types de Python. Len, ==, une boucle for fonctionnent sur un dictionnaire ou une liste, jamais sur un objet fait main tant que vous n'avez rien ajouté. Les méthodes magiques comblent cet écart en branchant votre classe sur cette même syntaxe.

Une méthode magique est une méthode dont le nom commence et se termine par deux tirets bas, comme __len__ ou __eq__. Vous ne l'appelez jamais vous-même : c'est Python qui la déclenche à votre place, dès qu'une syntaxe du langage rencontre votre objet. Écrire len(panier) revient à demander panier.__len__(), et la classe qui la définit gagne un comportement que les types natifs possèdent depuis toujours, comme l'illustre ce panier qui répond à len parce qu'il définit __len__, rien de plus.

PYTHON
class Panier:
    def __init__(self, articles):
        self.articles = articles

    def __len__(self):
        # Cette méthode répond à ce que demande len(panier)
        return len(self.articles)

panier = Panier(["stylo", "cahier"])
print(len(panier))
# 2

On les appelle aussi méthodes spéciales, ou dunder methods en anglais, pour double underscore. Le mot magique décrit l'effet ressenti, jamais le mécanisme : c'est une convention de nommage que l'interpréteur connaît par cœur et consulte au bon moment.


Les familles à connaître

Une centaine de méthodes magiques existent dans Python, un chiffre qui peut donner le vertige : faut-il toutes les connaître avant d'écrire une classe utile ? Non, une dizaine couvre l'essentiel, réunies dans le tableau qui suit.

MéthodeDéclenchée par
__init__La création d'un objet
__repr__ et __str__repr et l'affichage
__eq__L'opérateur d'égalité
__lt__La comparaison stricte, et donc le tri
__getitem__L'accès entre crochets
__contains__Le test d'appartenance
__iter__La boucle for et tout ce qui parcourt
__enter__ et __exit__Le bloc with
__call__Les parenthèses posées après l'objet
Bon à savoir

Trier une liste d'objets demande en théorie plusieurs de ces méthodes de comparaison. Le décorateur total_ordering du module functools déduit celles qui manquent à partir de __eq__ et d'une seule autre, par exemple __lt__ : deux méthodes suffisent alors là où six seraient nécessaires.

Un objet devient parcourable dès qu'il expose __iter__, qu'il hérite ou non d'une liste. Un gestionnaire de contexte n'est rien de plus qu'un objet qui expose __enter__ et __exit__, exactement ce que décrit le duck typing : ce qui compte n'est pas la classe dont vous héritez, mais ce que votre objet sait faire quand on le sollicite.


Le piège de l'égalité sans le hachage

Ajouter une méthode __eq__ semble n'engager à rien : vous décrivez comment deux objets se ressemblent. Pourtant cette ligne a un effet de bord que presque personne n'anticipe : Python retire silencieusement le hachage par défaut de la classe, et vos objets ne peuvent plus entrer dans un ensemble ni servir de clé de dictionnaire.

PYTHON
class Client:
    def __init__(self, email):
        self.email = email

    def __eq__(self, autre):
        # Deux clients sont égaux si leur email l'est, rien d'autre
        return self.email == autre.email

{Client("marie@exemple.fr")}
# TypeError: unhashable type: 'Client'
Attention

L'erreur n'apparaît jamais en écrivant __eq__, elle attend le jour où quelqu'un range vos objets dans un set ou un dict. Le message TypeError: unhashable type pointe alors vers une ligne sans rapport avec la cause réelle.

La correction tient en une méthode de plus, __hash__, calculée sur les mêmes attributs que l'égalité : deux objets égaux doivent produire le même hachage, sans quoi un dictionnaire pourrait ranger deux fois la même clé sans s'en apercevoir. Une dataclass déclarée avec frozen=True génère les deux méthodes pour vous, et c'est souvent la meilleure réponse.


Deux représentations pour deux lecteurs

Sans rien écrire, afficher un de vos objets dans la console renvoie une ligne du genre <Client object at 0x7f...>, qui ne dit rien du contenu de l'objet. Deux méthodes magiques permettent de reprendre la main sur ce texte, et elles ne s'adressent pas au même lecteur.

__repr__ parle au développeur : elle apparaît dans la console, dans les messages d'erreur et dans l'affichage d'une liste d'objets, et devrait montrer de quoi l'objet est fait. __str__ parle à l'utilisateur final, et c'est elle qu'un affichage ordinaire produit.

PYTHON
class Duree:
    def __init__(self, minutes):
        self.minutes = minutes

    def __repr__(self):
        # Pour déboguer : on veut voir la valeur brute
        return f"Duree(minutes={self.minutes})"

    def __str__(self):
        # Pour afficher : on veut un format lisible
        return f"{self.minutes // 60} h {self.minutes % 60}"

Si vous n'en écrivez qu'une seule, écrivez __repr__ : Python s'en sert comme repli quand __str__ manque, alors que l'inverse n'est jamais vrai. Une classe sans représentation affiche une adresse mémoire, qui ne dit rien pendant un débogage et transforme une liste de résultats en devinette.


Questions fréquentes

Question

Peut-on appeler une méthode magique directement ?

Techniquement oui, panier.__len__() fonctionne. En pratique, mieux vaut ne jamais le faire : la syntaxe du langage lève une TypeError explicite quand la méthode manque, là où l'appel direct produit une erreur d'attribut bien plus difficile à interpréter.

Question

Combien faut-il en définir sur une classe ?

Le moins possible, et __repr__ presque toujours puisqu'il aide au débogage sans coût. Une méthode magique n'a de sens que si la syntaxe qu'elle capte dit vraiment quelque chose de l'objet : donner une addition à ce qui ne s'additionne pas dans la vraie vie rend le code illisible.

Question

Pourquoi mon objet refuse-t-il de se laisser parcourir par une boucle ?

Parce qu'il ne définit ni __iter__ ni __getitem__, les deux seules portes que Python essaie avant d'abandonner et de lever une erreur.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.