web-dev-qa-db-fra.com

Quand "i + = x" est-il différent de "i = i + x" en Python?

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?

202
MarJamRob

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 .

306
mgilson

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

66
abarnert

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]
5
Deqing