La surcharge d'opérateurs
En Python, les opérateurs comme +, -, < fonctionnent sur les types natifs (int, str…). La surcharge d'opérateurs permet de redéfinir leur comportement pour les classes personnalisées, via des méthodes spéciales appelées dunder methods (double underscore).
__methode__) pour qu'un opérateur (+, ==, <…) ait un comportement adapté aux objets de cette classe. Python traduit automatiquement a + b en a.__add__(b).Exemple n°1 — Sans surcharge : erreur TypeError
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(2, 4)
p2 = Point(5, 1)
p3 = p1 + p2 # ❌ Python ne sait pas additionner deux PointsTypeError: unsupported operand type(s) for +: 'Point' and 'Point'
p1 + p2, il cherche la méthode __add__ dans la classe de p1. Si elle n'existe pas, il lève une TypeError. La surcharge consiste à définir cette méthodepour lui donner un sens.p1 + p2 → p1.__add__(p2)
p1 < p2 → p1.__lt__(p2)
p1 == p2 → p1.__eq__(p2)
print(p1) → p1.__str__()Surcharge des opérateurs arithmétiques
| Opérateur | Expression | Méthode spéciale | Description |
|---|---|---|---|
+ | p1 + p2 | __add__(self, other) | Addition |
- | p1 - p2 | __sub__(self, other) | Soustraction |
* | p1 * p2 | __mul__(self, other) | Multiplication |
** | p1 ** p2 | __pow__(self, other) | Puissance |
/ | p1 / p2 | __truediv__(self, other) | Division réelle |
// | p1 // p2 | __floordiv__(self, other) | Division entière |
% | p1 % p2 | __mod__(self, other) | Modulo (reste) |
- (unaire) | -p1 | __neg__(self) | Négation |
<< | p1 << p2 | __lshift__(self, other) | Décalage gauche |
>> | p1 >> p2 | __rshift__(self, other) | Décalage droit |
& | p1 & p2 | __and__(self, other) | ET binaire |
| | p1 | p2 | __or__(self, other) | OU binaire |
^ | p1 ^ p2 | __xor__(self, other) | XOR binaire |
~ | ~p1 | __invert__(self) | NON binaire (unaire) |
Exemple n°2 — Surcharge de +, -, * sur la classe Vecteur
class Vecteur:
"""Vecteur 2D avec surcharge des opérateurs arithmétiques."""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Vecteur({self.x}, {self.y})"
# Addition de deux vecteurs
def __add__(self, other):
return Vecteur(self.x + other.x, self.y + other.y)
# Soustraction de deux vecteurs
def __sub__(self, other):
return Vecteur(self.x - other.x, self.y - other.y)
# Multiplication par un scalaire : v * k
def __mul__(self, k):
return Vecteur(self.x * k, self.y * k)
# Multiplication inversée : k * v
def __rmul__(self, k):
return self.__mul__(k)
# Négation : -v
def __neg__(self):
return Vecteur(-self.x, -self.y)
v1 = Vecteur(2, 4)
v2 = Vecteur(5, 1)
print(f"v1 = {v1}")
print(f"v2 = {v2}")
print(f"v1 + v2 = {v1 + v2}")
print(f"v1 - v2 = {v1 - v2}")
print(f"v1 * 3 = {v1 * 3}")
print(f"2 * v2 = {2 * v2}")
print(f"-v1 = {-v1}")v1 = (2, 4) v2 = (5, 1) v1 + v2 = (7, 5) v1 - v2 = (-3, 3) v1 * 3 = (6, 12) 2 * v2 = (10, 2) -v1 = (-2, -4)
__rmul__ pour la commutativité v * 3 appelle v.__mul__(3), mais 3 * v appelle d'abord int.__mul__(v) qui échoue, puis Python essaie v.__rmul__(3). Définir __rmul__rend la multiplication commutative :def __rmul__(self, k):
return self.__mul__(k) # délègue à __mul__Surcharge des opérateurs de comparaison
| Opérateur | Expression | Méthode spéciale | Signification |
|---|---|---|---|
== | p1 == p2 | __eq__(self, other) | Égalité |
!= | p1 != p2 | __ne__(self, other) | Différence |
< | p1 < p2 | __lt__(self, other) | Strictement inférieur |
<= | p1 <= p2 | __le__(self, other) | Inférieur ou égal |
> | p1 > p2 | __gt__(self, other) | Strictement supérieur |
>= | p1 >= p2 | __ge__(self, other) | Supérieur ou égal |
Exemple n°3 — Classe Point avec comparaisons par distance à l'origine
import math
class Point:
"""Point 2D avec surcharge complète des opérateurs."""
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def norme(self):
"""Distance du point à l'origine."""
return math.sqrt(self.x**2 + self.y**2)
def __str__(self):
return f"({self.x}, {self.y})"
# ── Opérateurs arithmétiques ──────────────
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
# ── Opérateurs de comparaison (par norme) ─
def __eq__(self, other):
return self.norme() == other.norme()
def __ne__(self, other):
return self.norme() != other.norme()
def __lt__(self, other):
return self.norme() < other.norme()
def __le__(self, other):
return self.norme() <= other.norme()
def __gt__(self, other):
return self.norme() > other.norme()
def __ge__(self, other):
return self.norme() >= other.norme()
p1 = Point(2, 4) # norme ≈ 4.47
p2 = Point(5, 1) # norme ≈ 5.10
p3 = Point(2, 4) # identique à p1
print(f"p1 = {p1} | norme = {p1.norme():.4f}")
print(f"p2 = {p2} | norme = {p2.norme():.4f}")
print(f"p3 = {p3} | norme = {p3.norme():.4f}")
print()
print(f"p1 + p2 = {p1 + p2}")
print(f"p2 - p1 = {p2 - p1}")
print()
print(f"p1 == p3 : {p1 == p3}") # True — même norme
print(f"p1 == p2 : {p1 == p2}") # False
print(f"p1 < p2 : {p1 < p2}") # True — p1 plus proche
print(f"p2 > p1 : {p2 > p1}") # True
print(f"p1 <= p3 : {p1 <= p3}") # True — égaux
# Tri d'une liste de points
points = [Point(3, 4), Point(1, 1), Point(5, 0), Point(2, 2)]
points.sort() # utilise __lt__
print(f"\nPoints triés par distance : {[str(p) for p in points]}")p1 = (2, 4) | norme = 4.4721 p2 = (5, 1) | norme = 5.0990 p3 = (2, 4) | norme = 4.4721 p1 + p2 = (7, 5) p2 - p1 = (3, -3) p1 == p3 : True p1 == p2 : False p1 < p2 : True p2 > p1 : True p1 <= p3 : True Points triés par distance : ['(1, 1)', '(2, 2)', '(5, 0)', '(3, 4)']
sort() et min() gratuits Définir __lt__ suffit pour utiliser sort(), min() et max() sur une liste d'objets. Définir __eq__ et __lt__couvre la majorité des besoins :points.sort() # utilise __lt__
print(min(points)) # utilise __lt__
print(max(points)) # utilise __lt__== par défaut compare les identités Sans __eq__, Python compare les adresses mémoire, pas les valeurs. Deux objets distincts avec les mêmes données seront considérés différents :class Point:
def __init__(self, x, y): self.x, self.y = x, y
p1 = Point(2, 4)
p2 = Point(2, 4)
print(p1 == p2) # ❌ False — deux objets différents en mémoire
# ✅ Définir __eq__ pour comparer les valeursAutres méthodes spéciales utiles
| Méthode | Appelée par | Description |
|---|---|---|
__str__(self) | print(obj), str(obj) | Représentation lisible pour l'utilisateur |
__repr__(self) | repr(obj), débogueur | Représentation technique pour le développeur |
__len__(self) | len(obj) | Taille/longueur de l'objet |
__abs__(self) | abs(obj) | Valeur absolue (norme pour un vecteur) |
__bool__(self) | bool(obj), if obj: | Valeur de vérité de l'objet |
__getitem__(self, i) | obj[i] | Accès par indice ou clé |
__contains__(self, x) | x in obj | Test d'appartenance |
Exemple n°4 — Classe Vecteur complète avec méthodes spéciales
import math
class Vecteur:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Vecteur({self.x}, {self.y})"
def __add__(self, other):
return Vecteur(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vecteur(self.x - other.x, self.y - other.y)
def __mul__(self, k):
return Vecteur(self.x * k, self.y * k)
def __rmul__(self, k):
return self.__mul__(k)
def __neg__(self):
return Vecteur(-self.x, -self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __abs__(self):
return math.sqrt(self.x**2 + self.y**2) # norme
def __bool__(self):
return self.x != 0 or self.y != 0 # False si vecteur nul
def __getitem__(self, i):
if i == 0: return self.x
if i == 1: return self.y
raise IndexError("Indice doit être 0 (x) ou 1 (y)")
v = Vecteur(3, 4)
print(f"v = {v}")
print(f"abs(v) = {abs(v)}") # norme = 5.0
print(f"bool(v) = {bool(v)}") # True
print(f"v[0] = {v[0]}") # x
print(f"v[1] = {v[1]}") # y
nul = Vecteur(0, 0)
print(f"bool(nul) = {bool(nul)}") # False — vecteur nulv = (3, 4) abs(v) = 5.0 bool(v) = True v[0] = 3 v[1] = 4 bool(nul) = False
Discussion (0)
Soyez le premier à laisser un commentaire !
Laisser un commentaire
Votre commentaire sera visible après modération.