Surcharge des opérateurs en Python

02 Sep 2019 02 Sep 2019 18645 vues ESSADDOUKI Mostafa 8 min de lecture

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

Définition — Surcharge d'opérateurs La surcharge d'opérateurs consiste à définir dans une classe des méthodes spéciales (nommées __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 Points
Sortie
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'
Principe — Traduction automatique des opérateurs Quand Python rencontre 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érateurExpressionMéthode spécialeDescription
+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}")
Sortie
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)
Astuce — __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érateurExpressionMéthode spécialeSignification
==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]}")
Sortie
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)']
Astuce — 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__
Attention — == 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 valeurs

Autres méthodes spéciales utiles

MéthodeAppelée parDescription
__str__(self)print(obj), str(obj)Représentation lisible pour l'utilisateur
__repr__(self)repr(obj), débogueurRepré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 objTest 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 nul
Sortie
v         = (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.