Skip to main content
Niveau : Avancé

Module : Méthodes Spéciales (Dunder Methods)

1. Quoi : Les Méthodes Spéciales

Les méthodes spéciales, aussi appelées "méthodes magiques" ou "dunder methods" (pour Double Underscore), sont des méthodes prédéfinies en Python que vous pouvez ajouter à vos classes pour qu'elles s'intègrent avec les fonctionnalités natives du langage.

Leur nom est toujours entouré de doubles underscores (ex: __init__, __str__, __len__). Vous en connaissez déjà une : __init__, le constructeur.

En implémentant ces méthodes, vous permettez à vos objets de se comporter comme des types de données intégrés, en répondant à des opérateurs (+, ==), des fonctions (len(), str()), et d'autres syntaxes.

2. Pourquoi : Rendre les objets plus "pythonic"

L'utilisation des méthodes spéciales rend vos objets plus intuitifs et plus faciles à utiliser.

  • print(my_object) peut afficher une description lisible au lieu de <__main__.MyClass object at 0x...>.
  • len(my_collection) peut retourner le nombre d'éléments de votre objet collection personnalisé.
  • obj1 + obj2 peut effectuer une addition logique entre deux de vos objets.
  • if my_object: peut évaluer la "vérité" de votre objet.

3. Comment : Les Méthodes Spéciales les plus courantes

A. Représentation d'objet : __str__ et __repr__

  • __str__(self) : Doit retourner une chaîne de caractères lisible par l'humain. C'est ce qui est appelé par print(obj) et str(obj). Le but est l'affichage.
  • __repr__(self) : Doit retourner une représentation non ambiguë de l'objet, idéalement une chaîne qui pourrait être utilisée pour recréer l'objet (ex: MyClass(arg1=value1)). C'est ce qui est affiché dans la console interactive si vous tapez juste le nom de l'objet. C'est aussi le fallback si __str__ n'est pas défini.

Bonne pratique : Implémentez toujours __repr__. Implémentez __str__ si vous voulez une version plus "jolie" pour l'affichage.

class Book:
def __init__(self, title, author):
self.title = title
self.author = author

def __str__(self):
return f'"{self.title}" by {self.author}'

def __repr__(self):
return f"Book(title='{self.title}', author='{self.author}')"

book = Book("The Hobbit", "J.R.R. Tolkien")

print(book) # Appelle __str__ -> "The Hobbit" by J.R.R. Tolkien
print(str(book)) # Appelle __str__
print(repr(book)) # Appelle __repr__ -> Book(title='The Hobbit', author='J.R.R. Tolkien')
# Dans une console interactive, taper `book` appellerait __repr__

B. Comportement de collection : __len__ et __getitem__

  • __len__(self) : Doit retourner la "longueur" de l'objet, un entier. Permet d'utiliser la fonction len(obj).
  • __getitem__(self, key) : Permet d'accéder à un élément via un index ou une clé, comme pour les listes ou les dictionnaires (obj[key]).
class Deck:
def __init__(self):
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = '♠♡♢♣'
self.cards = [f"{r}{s}" for s in suits for r in ranks]

def __len__(self):
return len(self.cards)

def __getitem__(self, position):
return self.cards[position]

deck = Deck()
print(len(deck)) # Appelle __len__ -> 52
print(deck[0]) # Appelle __getitem__(0) -> 2♠
print(deck[-1]) # Appelle __getitem__(-1) -> A♣

C. Opérateurs de comparaison : __eq__

  • __eq__(self, other) : Définit le comportement de l'opérateur d'égalité ==. Doit retourner True ou False.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
# Deux points sont égaux si leurs coordonnées x et y sont égales
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2) # Appelle __eq__ -> True
print(p1 == p3) # Appelle __eq__ -> False
print(p1 == (1, 2)) # False (grâce à isinstance)

D'autres opérateurs existent : __ne__ (!=), __lt__ (<), __gt__ (>), etc.

D. Opérateurs arithmétiques : __add__

  • __add__(self, other) : Définit le comportement de l'opérateur d'addition +.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return f"Vector({self.x}, {self.y})"

def __add__(self, other):
# L'addition de deux vecteurs est l'addition de leurs composantes
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)

v1 = Vector(2, 4)
v2 = Vector(1, 3)
v3 = v1 + v2 # Appelle __add__

print(v3) # Vector(3, 7)

D'autres opérateurs existent : __sub__ (-), __mul__ (*), etc.

E. Valeur booléenne : __bool__

  • __bool__(self) : Définit la valeur de vérité de l'objet lorsqu'il est utilisé dans un contexte booléen (if obj:). Doit retourner True ou False. Si non défini, Python utilise __len__ (un objet de longueur 0 est False).
class ShoppingCart:
def __init__(self):
self.items = []

def __bool__(self):
# Le panier est "vrai" s'il n'est pas vide
return len(self.items) > 0

cart = ShoppingCart()
if not cart: # Appelle __bool__
print("Your cart is empty.") # S'affiche

cart.items.append("apple")
if cart: # Appelle __bool__
print("You have items in your cart.") # S'affiche

Exercice 01 : Création d'une Classe Vector

Objectif

Cet exercice a pour but de vous faire implémenter plusieurs méthodes spéciales pour créer une classe Vector (vecteur 2D) qui se comporte de manière intuitive avec les opérateurs Python natifs.

Contexte

Vous allez créer une classe Vector qui représente un vecteur dans un plan 2D, avec des composantes x et y. Vous la rendrez plus "pythonic" en implémentant des méthodes spéciales pour :

  • L'afficher de manière lisible (__str__ et __repr__).
  • L'additionner avec un autre vecteur (__add__).
  • Tester son égalité avec un autre vecteur (__eq__).

Énoncé

  1. Créez un nouveau fichier Python nommé vector_class.py.

  2. Définissez la classe Vector.

    • Son constructeur __init__ doit accepter x et y et les stocker comme attributs.
  3. Implémentez __repr__(self) :

    • Cette méthode doit retourner une chaîne de caractères qui pourrait être utilisée pour recréer l'objet.
    • Exemple de retour : Vector(x=3, y=4)
  4. Implémentez __str__(self) :

    • Cette méthode doit retourner une représentation plus simple et lisible par un humain.
    • Exemple de retour : (3, 4)
  5. Implémentez __eq__(self, other) :

    • Cette méthode définit le comportement de l'opérateur ==.
    • Elle doit retourner True si other est aussi une instance de Vector ET que leurs composantes x et y sont égales. Sinon, elle retourne False.
  6. Implémentez __add__(self, other) :

    • Cette méthode définit le comportement de l'opérateur +.
    • L'addition de deux vecteurs v1(x1, y1) et v2(x2, y2) est un nouveau vecteur v3(x1+x2, y1+y2).
    • La méthode doit vérifier si other est bien une instance de Vector. Si ce n'est pas le cas, elle doit retourner NotImplemented (une constante spéciale qui indique à Python que l'opération n'est pas supportée entre ces types).
    • Si other est un Vector, la méthode doit retourner une nouvelle instance de Vector représentant la somme.
  7. Testez votre classe :

    • Créez deux vecteurs v1 = Vector(2, 3) et v2 = Vector(5, 1).
    • Testez print(), str(), et repr().
    • Testez l'addition v3 = v1 + v2 et affichez v3.
    • Testez l'égalité v1 == v2 et v1 == Vector(2, 3).

Résultat Attendu

v1 string representation: (2, 3)
v1 official representation: Vector(x=2, y=3)

v3 = v1 + v2
v3 string representation: (7, 4)

v1 == v2: False
v1 == Vector(2, 3): True
v1 == (2, 3): False
Cliquez ici pour voir un exemple de code de solution
# vector_class.py

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
"""Official representation, good for debugging."""
return f"Vector(x={self.x}, y={self.y})"

def __str__(self):
"""User-friendly string representation."""
return f"({self.x}, {self.y})"

def __eq__(self, other):
"""Equality comparison (==)."""
if not isinstance(other, Vector):
return False
return self.x == other.x and self.y == other.y

def __add__(self, other):
"""Addition (+)."""
if not isinstance(other, Vector):
return NotImplemented
# Return a new Vector instance
return Vector(self.x + other.x, self.y + other.y)

# --- Testing ---
v1 = Vector(2, 3)
v2 = Vector(5, 1)

# Test __str__ and __repr__
print(f"v1 string representation: {str(v1)}")
print(f"v1 official representation: {repr(v1)}")
print("-" * 20)

# Test __add__
print("v3 = v1 + v2")
v3 = v1 + v2
print(f"v3 string representation: {v3}")
print("-" * 20)

# Test __eq__
print(f"v1 == v2: {v1 == v2}")
print(f"v1 == Vector(2, 3): {v1 == Vector(2, 3)}")
print(f"v1 == (2, 3): {v1 == (2, 3)}") # Should be False thanks to isinstance check