__add__ et __iadd__ définissent deux comportements distincts pour l’addition en Python : l’un crée un nouvel objet, l’autre modifie l’existant. Cette distinction a des conséquences réelles sur les alias et les références partagées, et elle réserve des surprises même aux développeurs expérimentés.

add : l’addition qui crée

__add__ est appelée par l’opérateur +. Elle doit retourner un nouvel objet et laisser les opérandes inchangés.

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

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

a = Vector(1, 2)
b = Vector(3, 4)
c = a + b  # nouvel objet Vector(4, 6)

print(a.x, a.y)          # 1 2 — a est inchangé
print(id(a) == id(c))    # False — objets distincts

a + b appelle a.__add__(b). Si __add__ n’est pas défini sur a ou retourne NotImplemented, Python essaie b.__radd__(a).

iadd : l’addition qui modifie

__iadd__ est appelée par l’opérateur +=. Elle modifie l’objet en place et retourne self.

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

    def __iadd__(self, other):
        self.x += other.x
        self.y += other.y
        return self  # obligatoire

a = Vector(1, 2)
b = Vector(3, 4)
original_id = id(a)

a += b

print(a.x, a.y)                  # 4 6
print(id(a) == original_id)      # True — même objet

Retourner self est non négociable : Python réassigne le résultat de __iadd__ à la variable gauche. Oublier le return self revient à écrire a = None.

Le piège des alias

La distinction copie/référence devient critique quand plusieurs variables pointent vers le même objet.

a = Vector(1, 2)
alias = a  # alias et a pointent vers le même objet

# Avec __add__ : a + b crée un nouveau Vector
a = a + Vector(3, 4)
print(id(a) == id(alias))   # False — a pointe vers un nouvel objet
print(alias.x, alias.y)     # 1 2 — alias pointe toujours vers l'original

# Avec __iadd__ : a += b modifie l'objet en place
a = Vector(1, 2)
alias = a
a += Vector(3, 4)
print(id(a) == id(alias))   # True — même objet
print(alias.x, alias.y)     # 4 6 — alias voit le changement

Dans un contexte où l’objet est partagé entre plusieurs références (attribut de classe, argument passé à une fonction, élément dans une liste), += avec __iadd__ propage la modification à tous les points d’accès.

Le fallback silencieux

Si __iadd__ n’est pas défini, Python utilise __add__ comme fallback pour +=. L’opération crée alors un nouvel objet au lieu de modifier l’existant.

class Counter:
    def __init__(self, n):
        self.n = n

    def __add__(self, other):
        return Counter(self.n + other.n)
    # pas de __iadd__

a = Counter(5)
original_id = id(a)
a += Counter(3)
print(id(a) == original_id)  # False — nouvel objet, __add__ utilisé en fallback

Ce comportement est correct mais peut surprendre : a += b se comporte comme a = a + b plutôt que comme une modification en place.

Types immutables : += toujours par copie

Les types immutables (int, str, tuple) n’ont pas de __iadd__. L’opérateur += crée systématiquement un nouvel objet.

a = "hello"
original_id = id(a)
a += " world"
print(id(a) == original_id)  # False — nouvelle chaîne

x = 10
ref = x
x += 1
print(ref)  # 10 — ref pointe toujours vers l'ancien int

Types mutables : += modifie en place

Les types mutables comme list ont un __iadd__ qui étend la liste en place via extend(). La différence avec + n’est pas cosmétique.

a = [1, 2]
alias = a

a += [3]        # appelle list.__iadd__, modifie en place
print(alias)    # [1, 2, 3] — alias voit le changement

a = [1, 2]
alias = a
a = a + [3]     # appelle list.__add__, crée une nouvelle liste
print(alias)    # [1, 2] — alias pointe toujours vers l'ancienne liste

Quand implémenter chacun

CasRecommandation
Objet immutable par design__add__ uniquement
Objet mutable avec état interne__add__ + __iadd__
__iadd__ sans __add__Déconseillé : + devient inutilisable

Si l’objet est mutable et que la modification en place est sémantiquement correcte, implémenter __iadd__ évite des copies inutiles et rend l’intention explicite.