Complément - niveau intermédiaire¶
Nous avons vu en première semaine (Séquence “Les types numériques”) une première introduction à l’instruction += et ses dérivées comme *=, **=, etc.
Ces constructions ont une définition à géométrie variable¶
En C quand on utilise += (ou encore ++) on modifie la mémoire en place - historiquement, cet opérateur permettait au programmeur d’aider à l’optimisation du code pour utiliser les instructions assembleur idoines.
Ces constructions en Python s’inspirent clairement de C, aussi dans l’esprit ces constructions devraient fonctionner en modifiant l’objet référencé par la variable.
Mais les types numériques en Python ne sont pas mutables, alors que les listes le sont. Du coup le comportement de += est différent selon qu’on l’utilise sur un nombre ou sur une liste, ou plus généralement selon qu’on l’invoque sur un type mutable ou non. Voyons cela sur des exemples très simples :
# Premier exemple avec un entier
# on commence avec une référence partagée
a = b = 3
a is bTrue# on utilise += sur une des deux variables
a += 1
# ceci n'a pas modifié b
# c'est normal, l'entier n'est pas mutable
print(a)
print(b)
print(a is b)4
3
False
# Deuxième exemple, cette fois avec une liste
# la même référence partagée
a = b = []
a is bThe history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
True# pareil, on fait += sur une des variables
a += [1]
# cette fois on a modifié a et b
# car += a pu modifier la liste en place
print(a)
print(b)
print(a is b)[1]
[1]
True
Vous voyez donc que la sémantique de += (c’est bien entendu le cas pour toutes les autres formes d’instructions qui combinent l’affectation avec un opérateur) est différente suivant que l’objet référencé par le terme de gauche est mutable ou immuable.
Pour cette raison, c’est là une opinion personnelle, cette famille d’instructions n’est pas le trait le plus réussi dans le langage, et je ne recommande pas de l’utiliser.
Précision sur la définition de +=¶
Nous avions dit en première semaine, et en première approximation, que :
x += yétait équivalent à :
x = x + yAu vu de ce qui précède, on voit que ce n’est pas tout à fait exact, puisque :
# si on fait x += y sur une liste
# on fait un effet de bord sur la liste
# comme on vient de le voir
a = []
print("avant", id(a))
a += [1]
print("après", id(a))avant 140570053894464
après 140570053894464
# alors que si on fait x = x + y sur une liste
# on crée un nouvel objet liste
a = []
print("avant", id(a))
a = a + [1]
print("après", id(a))avant 140570053895040
après 140570053894464
Vous voyez donc que vis-à-vis des références partagées, ces deux façons de faire mènent à un résultat différent.