On m'a dit que +=
peut avoir des effets différents de ceux de la notation standard i = i +
. Y a-t-il un cas dans lequel i += 1
serait différent de i = i + 1
?
Cela dépend entièrement de l'objet i
.
+=
Appelle la méthode __iadd__
(si elle existe - retombe sur __add__
Si elle n'existe pas) alors que +
appelle la méthode __add__
1 ou la méthode __radd__
dans quelques cas2.
Du point de vue de l'API, __iadd__
Est censé être utilisé pour modifier des objets mutables à la place (en retournant l'objet qui a été muté) alors que __add__
Devrait renvoyer a = nouvelle instance de quelque chose. Pour les objets immutable, les deux méthodes renvoient une nouvelle instance, mais __iadd__
Placera la nouvelle instance dans l'espace de noms actuel avec le même nom que l'ancien. C'est pourquoi
i = 1
i += 1
semble incrémenter i
. En réalité, vous obtenez un nouvel entier et vous l’attribuez "en plus de" i
- vous perdez une référence à l’ancien entier. Dans ce cas, i += 1
Est exactement identique à i = i + 1
. Mais, avec la plupart des objets mutables, c'est une histoire différente:
À titre d'exemple concret:
a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a #[1, 2, 3, 1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
par rapport à:
a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]
remarquez comment dans le premier exemple, puisque b
et a
font référence au même objet, lorsque j’utilise +=
sur b
, il change réellement b
(et a
voit également ce changement - après tout, il fait référence à la même liste). Dans le second cas cependant, lorsque je fais b = b + [1, 2, 3]
, Cela prend la liste que b
fait référence et la concatène avec une nouvelle liste [1, 2, 3]
. Il stocke ensuite la liste concaténée dans l'espace de noms actuel sous la forme b
- Sans se soucier de ce que b
était auparavant.
1Dans l'expression x + y
, Si x.__add__
N'est pas implémenté ou si x.__add__(y)
renvoie NotImplemented
et x
et y
ont des types différents, alors x + y
essaie d'appeler y.__radd__(x)
. Donc, dans le cas où vous avez
foo_instance += bar_instance
si Foo
n'implémente pas __add__
ou __iadd__
, le résultat obtenu est le même que
foo_instance = bar_instance.__radd__(bar_instance, foo_instance)
2Dans l'expression foo_instance + bar_instance
, bar_instance.__radd__
Sera essayé avant foo_instance.__add__
si le type de bar_instance
Est une sous-classe du type de foo_instance
(par exemple, issubclass(Bar, Foo)
). La raison en est que Bar
est en quelque sorte un objet "de niveau supérieur" à Foo
donc Bar
devrait avoir la possibilité de remplacer le comportement de Foo
.
Sous les couvertures, i += 1
fait quelque chose comme ça:
try:
i = i.__iadd__(1)
except AttributeError:
i = i.__add__(1)
Tandis que i = i + 1
fait quelque chose comme ça:
i = i.__add__(1)
Ceci est une légère simplification excessive, mais vous avez l’idée: Python donne aux types un moyen de gérer +=
_ spécialement en créant un __iadd__
méthode ainsi qu'un __add__
.
L’intention est que les types mutables, comme list
, muteront eux-mêmes dans __iadd__
(puis retourne self
, à moins que vous ne fassiez quelque chose de très délicat), tandis que les types immuables, comme int
, ne le mettront tout simplement pas en œuvre.
Par exemple:
>>> l1 = []
>>> l2 = l1
>>> l1 += [3]
>>> l2
[3]
Parce que l2
est le même objet que l1
, et vous avez muté l1
, vous avez également muté l2
.
Mais:
>>> l1 = []
>>> l2 = l1
>>> l1 = l1 + [3]
>>> l2
[]
Ici, vous n'avez pas muté l1
; à la place, vous avez créé une nouvelle liste, l1 + [3]
et rebondir le nom l1
pour pointer dessus, laissant l2
montrant la liste originale.
(Dans le +=
version, vous avez également relié l1
, c'est juste que dans ce cas, vous le réassigniez au même list
auquel il était déjà lié, vous pouvez donc généralement ignorer cette partie.)
Voici un exemple qui compare directement i += x
avec i = i + x
:
def foo(x):
x = x + [42]
def bar(x):
x += [42]
c = [27]
foo(c); # c is not changed
bar(c); # c is changed to [27, 42]