Apprenez en lisant dans l'ordre

Méthodes spéciales — Apprendre à ta classe comment se comportent + et print

Apprends les méthodes spéciales de Python (méthodes dunder). Personnalise + avec __add__, contrôle la sortie de print avec __str__, et redéfinis == avec __eq__ — le tout avec des schémas.

La dernière fois on a trié les méthodes d'instance, de classe et statiques. Cette fois on couvre un autre genre — les méthodes spéciales (alias méthodes dunder).

Que sont les méthodes spéciales ? — « Méthodes dunder »

Les méthodes comme __init__ et __str__ — des noms encadrés par deux underscores de chaque côté — sont appelées méthodes spéciales. Comme elles sont entre double underscores, on les appelle aussi méthodes dunder.

Le point clé sur les méthodes spéciales, c'est que tu ne les appelles pas directement. Python les appelle automatiquement quand tu effectues certaines opérations. Écris v1 + v2 et Python appelle v1.__add__(v2) en coulisses. Écris print(p) et p.__str__() est appelée. Écris a == b et a.__eq__(b) s'exécute.

Les opérateurs et leurs méthodes spéciales
Ce que tuécrisPythonconvertitMéthodeappeléev1 + v2__add__print(p)__str__a == b__eq__
Les opérateurs et fonctions natives de Python appellent les méthodes dunder correspondantes en coulisses. Définis-les sur ta classe et ton propre type peut utiliser +, ==, et compagnie.

__add__ — Définir l'opérateur +

Prenons + comme exemple évident. Suppose que tu as une classe Money pour des montants en euros, et que tu veux que Money(300) + Money(500) te donne Money(800). Essaie de les additionner d'emblée et Python lèvera une TypeError en se plaignant qu'il ne sait pas comment additionner Money à Money.

La façon d'apprendre à Python à les additionner, c'est la méthode __add__. Définis def __add__(self, other): et renvoie une nouvelle instance construite à partir de self (côté gauche) et other (côté droit). Maintenant + fonctionne sur ta classe.

class Money:
    def __init__(self, amount):
        self.amount = amount

    def __add__(self, other):                 # appelé quand on écrit +
        return Money(self.amount + other.amount)

wallet  = Money(300)
payment = Money(500)
total   = wallet + payment                    # vraiment wallet.__add__(payment)
print(total.amount)                           # 800
Ce que v1 + v2 fait en coulisses
wallet+ paymentwallet.__add__(payment)self =walletother =paymentreturn Money(self.amount + other.amount)renvoieMoney(800)traduit
Écris + et Python appelle self.__add__(other). self est l'opérande gauche, other est celui de droite. Quelle que soit la nouvelle instance que tu renvoies, elle devient le résultat de +.

Implémente __add__ sur Money pour que + additionne deux soldes.

① Définis class Money: avec __init__(self, amount) qui affecte self.amount = amount.

② Définis __add__(self, other) qui renvoie Money(self.amount + other.amount).

③ Construis wallet = Money(300) et payment = Money(500), puis print de total.amounttotal = wallet + payment.

(Une fois que ça s'exécute correctement, l'explication apparaîtra.)

Éditeur Python

Exécuter le code pour voir le résultat

__str__ et __repr__ — Deux types de représentation en chaîne

Ensuite : les méthodes spéciales appelées quand une instance est convertie en chaîne. Il y en a deux, avec des objectifs différents.

- __str__ — appelée par print(p) et str(p). Une chaîne lisible, orientée utilisateur.

- __repr__ — appelée dans le REPL ou pour le débogage. Une chaîne proche du code, détaillée.

Sans surcharge, print(user) affiche quelque chose comme <__main__.User object at 0x...> — assez inutile. Définis __str__ pour un affichage propre, et définis aussi __repr__ pour que le débogage te montre le type et le contenu d'un coup d'œil.

class User:
    def __init__(self, name, age):
        self.name = name
        self.age  = age

    def __str__(self):                       # pour print() / str()
        return f"{self.name} ({self.age} ans)"

    def __repr__(self):                      # pour le débogage
        return f"User(name={self.name!r}, age={self.age})"

u = User("Alice", 30)
print(u)             # Alice (30 ans)               <- __str__
print(repr(u))       # User(name='Alice', age=30)   <- __repr__
Quand utiliser __str__ vs __repr__
__str__ (orientée utilisateur)
  • Appelée par print(u) et str(u)
  • Objectif : une chaîne à lire pour les utilisateurs finaux
  • Exemple : Alice (30 ans)
__repr__ (orientée développeur)
  • Appelée par repr(u) et le REPL interactif
  • Sert de repli quand __str__ est absente lors d'un print
  • Objectif : une chaîne de débogage montrant type et contenu
  • Exemple : User(name='Alice', age=30)
Définir les deux est idéal, mais si tu n'en définis qu'une, fais-en __repr__ — ça facilite énormément le débogage.

Implémente __str__ et __repr__ sur User et compare ce que print et repr affichent.

① Définis class User: avec __init__(self, name, age) qui affecte self.name / self.age.

② Définis __str__(self) renvoyant f"{self.name} ({self.age} ans)".

③ Définis __repr__(self) renvoyant f"User(name={self.name!r}, age={self.age})". Le !r après self.name signifie incorporer le résultat de repr() appliqué à la valeur — les chaînes sortent avec des apostrophes ('Alice') au lieu du texte brut (Alice).

④ Construis u = User("Alice", 30), puis exécute à la fois print(u) et print(repr(u)) pour voir la différence.

Éditeur Python

Exécuter le code pour voir le résultat

__eq__ — Définir ==

Maintenant l'égalité. Quand tu écris a == b, Python appelle a.__eq__(b) en interne. Si ta classe ne définit pas __eq__, le défaut est « sont-ils le même objet en mémoire ? » (une comparaison d'id).

Dis que tu as une classe Coupon et que tu veux que deux coupons avec le même code comptent comme le même coupon — même si les valeurs de remise diffèrent. C'est une égalité « niveau métier », et __eq__ est l'endroit où tu la formules.

Comment __eq__ change le comportement de ==
c1 == c2pas de__eq__test id(adr mémoire)avec__eq__self.code== other.codedéfautdéfini
Sans __eq__, == est une vérification d'id — même contenu mais instances séparées renvoient toujours False. Avec __eq__, tu peux comparer par contenu (code).
class Coupon:
    def __init__(self, code, discount):
        self.code     = code
        self.discount = discount

    def __eq__(self, other):
        return self.code == other.code        # même code = même coupon

c1 = Coupon("SPRING10", 0.10)
c2 = Coupon("SPRING10", 0.20)               # remise différente, même code
c3 = Coupon("SUMMER15", 0.15)

print(c1 == c2)   # True  (codes identiques)
print(c1 == c3)   # False (codes différents)

Que se passe-t-il sans __eq__ ?

Si tu ne définis pas __eq__, c1 == c2 vérifie s'ils sont le même objet en mémoire (pointant littéralement vers la même boîte). Même si chaque attribut correspond parfaitement, deux instances construites séparément vivent à des adresses mémoire différentes et le résultat est False. Quand tu veux « égal par contenu », définis __eq__ toi-même.

Implémente __eq__ sur Coupon pour que deux coupons soient égaux quand leur code correspond.

① Définis class Coupon: avec __init__(self, code, discount) qui affecte self.code et self.discount.

② Définis __eq__(self, other) qui renvoie self.code == other.code.

③ Construis trois coupons — Coupon("SPRING10", 0.10), Coupon("SPRING10", 0.20), et Coupon("SUMMER15", 0.15) — et print deux comparaisons ==.

Éditeur Python

Exécuter le code pour voir le résultat

Autres méthodes spéciales courantes — __call__ / __len__ / __bool__

Au-delà des quatre qu'on a couvertes, plusieurs autres méthodes spéciales reviennent souvent. Trois à connaître tout de suite :

- __call__ — rend une instance appelable comme une fonction (syntaxe obj(...))

- __len__ — définit ce que len(obj) renvoie

- __bool__ — définit comment l'instance s'évalue en valeur de vérité dans if obj: ou bool(obj)

Chacune apprend une opération native — appel de fonction, len(), le test de véracité de if — à ta classe personnalisée. Un Logger qui accumule des entrées de log est un excellent exemple pour mettre les trois sur une même classe.

class Logger:
    def __init__(self, name):
        self.name = name
        self.log  = []

    def __call__(self, message):                    # logger("...") fonctionne
        self.log.append(message)
        return f"[{self.name}] {message}"

    def __len__(self):                               # pour len(logger)
        return len(self.log)

    def __bool__(self):                              # pour if logger:
        return len(self.log) > 0

app = Logger("app")
print(app("Application démarrée"))    # [app] Application démarrée
print(app("Connexion réussie"))   # [app] Connexion réussie
print(len(app))               # 2
if app:
    print("Il y a des logs")    # Il y a des logs
Apprendre à Logger à parler la syntaxe native
app('msg')app.__call__('msg')len(app)app.__len__()if app:app.__bool__()devientdevientdevient
Une syntaxe familière — logger(...), len(logger), if logger: — est apprise à ta classe personnalisée via trois dunders correspondants.

Et si tu ne définis pas __bool__ ?

Quand __bool__ est absente, Python retombe sur __len__. Une longueur de 0 devient False ; tout le reste devient True. Si les deux sont absentes, les instances sont toujours vraies, peu importe leur état. C'est la même règle qui fait que les listes et chaînes vides s'évaluent à False.

Implémente les trois méthodes spéciales sur Logger pour que ajouter des messages, les compter, et vérifier la vacuité fonctionnent tous avec la syntaxe native.

① Définis class Logger: avec __init__(self, name) réglant self.name = name et self.log = [].

② Définis __call__(self, message) : ajoute message à self.log, puis renvoie f"[{self.name}] {message}".

③ Définis __len__(self) renvoyant len(self.log).

④ Définis __bool__(self) renvoyant len(self.log) > 0.

⑤ Construis error_log = Logger("error"), puis exécute dans l'ordre : print(bool(error_log))error_log("Erreur BDD")print(len(error_log))print(bool(error_log)).

Éditeur Python

Exécuter le code pour voir le résultat

Il y en a beaucoup d'autres — __hash__ (pour utilisation comme clé set/dict), __lt__ / __gt__ (les comparaisons < / >), __getitem__ (la syntaxe obj[key]), __iter__ (l'itération for x in obj:), et d'autres. Tu n'as pas besoin de toutes les mémoriser. Garde juste cette carte mentale : « si tu veux apprendre une opération native à ta classe, il y a probablement un dunder correspondant. » Puis cherche-le quand tu en as besoin.

Méthode spécialeSyntaxeQuand elle est appelée
__init__Money(300)À la création d'instance
__add__a + bOpérateur +
__str__print(p) / str(p)Chaîne orientée utilisateur
__repr__repr(p) / affichage REPLChaîne orientée développeur
__eq__a == bComparaison d'égalité
__call__obj(...)Appel de style fonction
__len__len(obj)Longueur
__bool__if obj: / bool(obj)Véracité
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Quand result = a + b s'exécute, quelle méthode Python appelle-t-il en coulisses ?

Question 2Quelle description de __str__ et __repr__ est la plus précise ?

Question 3Si une classe ne définit pas __eq__, que compare a == b ? (Les deux sont des instances de la même classe.)