2013-03-13 7 views
179

Mir wurde gesagt, dass += andere Effekte als die Standardnotation i = i + haben kann. Gibt es einen Fall, in dem sich i += 1 von i = i + 1 unterscheidet?Wann ist "i + = x" anders als "i = i + x" in Python?

+6

'+ =' verhält sich wie 'extend()' im Falle von Listen. –

+11

@AshwiniChaudhary Das ist eine ziemlich feine Unterscheidung, wenn man bedenkt, dass "i = [1,2,3]; i = i + [4,5,6]; i == [1,2,3,4,5,6]" ist "wahr". Viele Entwickler bemerken möglicherweise nicht, dass sich 'id (i) 'für eine Operation ändert, aber nicht für die andere. – kojiro

+1

@kojiro - Obwohl es eine subtile Unterscheidung ist, denke ich, dass es eine wichtige ist. – mgilson

Antwort

280

Dies hängt vollständig vom Objekt i ab.

+= ruft die __iadd__ method (wenn es vorhanden ist - wieder auf __add__ fallen, wenn es nicht vorhanden ist), während + ruft die __add__ method oder __radd__ method in a few cases .

Aus API Sicht ist __iadd__ soll zum Modifizieren veränderbare Objekte an Ort und Stelle verwendet werden (Rückgabe des Objekts, die mutiert wurde), während __add__ eine neue Instanz etwas zurückgeben sollte. Für unveränderliche Objekte geben beide Methoden eine neue Instanz zurück, aber __iadd__ wird die neue Instanz in den aktuellen Namespace mit demselben Namen wie die alte Instanz einfügen. Deshalb

i = 1 
i += 1 

scheint zu erhöhen i. In Wirklichkeit erhalten Sie eine neue Ganzzahl und weisen sie "über" i - Verlust einer Referenz auf die alte Ganzzahl zu. In diesem Fall ist i += 1 genau dasselbe wie i = i + 1. Aber bei den meisten veränderbaren Objekten, ist es eine andere Geschichte:

Als ein konkretes Beispiel:

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] 

im Vergleich zu:

a = [1, 2, 3] 
b = a 
b = b + [1, 2, 3] 
print a #[1, 2, 3] 
print b #[1, 2, 3, 1, 2, 3] 

merken, wie im ersten Beispiel, da b und a Referenz das gleiche Objekt, wenn ich += auf b benutze, ändert es sich tatsächlich b (und a sieht, dass sich auch ändern - schließlich referenziert es die gleiche Liste). Im zweiten Fall jedoch, wenn ich b = b + [1, 2, 3] mache, nimmt dies die Liste, die b referenziert und verkettet es mit einer neuen Liste [1, 2, 3]. Es speichert dann die verkettete Liste im aktuellen Namespace als b - ohne Rücksicht darauf, was b war die Zeile zuvor.


Im Ausdruck x + y, wenn x.__add__ nicht implementiert ist oder wenn x.__add__(y) kehrt NotImplementedundx und y haben verschiedene Arten, dann x + y versucht y.__radd__(x) zu nennen.Also, in dem Fall, in dem Sie haben

foo_instance += bar_instance

wenn Foo nicht __add__ oder __iadd__ nicht implementiert dann das Ergebnis hier ist die gleiche wie

foo_instance = bar_instance.__radd__(bar_instance, foo_instance)

In dem Ausdruck foo_instance + bar_instance wird bar_instance.__radd__ versucht, bevor foo_instance.__add__wenn der Typ bar_instance eine Unterklasse des Typs foo_instance ist (z.B. issubclass(Bar, Foo)). Der Grund dafür ist, dass Bar in gewissem Sinne ein "höheres" Objekt als Foo ist, so dass Bar die Option erhalten sollte, das Verhalten von Foo zu überschreiben.

+15

Nun, '+ =' ruft '_iaddd' _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _. Deshalb ist "i = 1; i + = 1' funktioniert, obwohl es kein 'int .__ iadd__' gibt. Aber anders als diese kleine Nisse, großartige Erklärungen. – abarnert

+3

@abarnert - Ich nahm immer an, dass 'int .__ iadd__' gerade '__add__' genannt wurde. Ich bin froh, heute etwas Neues gelernt zu haben :). – mgilson

+0

@abarnert - Ich nehme an, dass es vielleicht * komplett * ist, 'x + y' nennt' y .__ radd __ (x) 'wenn' x .__ add__' nicht existiert (oder 'NotImplemented' und' x' und ' y 'sind von verschiedenen Typen) – mgilson

63

unter der Decke, i += 1 tut etwas wie folgt aus:

try: 
    i = i.__iadd__(1) 
except AttributeError: 
    i = i.__add__(1) 

Während i = i + 1 ist so etwas wie dieses:

i = i.__add__(1) 

Dies ist eine leichte Simplifizierung, aber Sie bekommen die Idee: Python gibt gibt eine Möglichkeit, += speziell zu behandeln, indem eine __iadd__ Methode sowie eine __add__ erstellen.

Die Absicht ist, dass änderbare Typen, wie list, sich in __iadd__ mutieren wird (und dann self zurückkehren, wenn Sie etwas sehr heikel tun), während unveränderlichen Typen, wie int, wird es einfach nicht umsetzen.

Zum Beispiel:

>>> l1 = [] 
>>> l2 = l1 
>>> l1 += [3] 
>>> l2 
[3] 

Da l2 das gleiche Objekt wie l1 ist, und Sie mutiert l1, Sie mutiert auch l2.

Aber:

>>> l1 = [] 
>>> l2 = l1 
>>> l1 = l1 + [3] 
>>> l2 
[] 

Hier hast du nicht l1 mutieren; Stattdessen haben Sie eine neue Liste erstellt, l1 + [3], und den Namen l1 zurückspringen, um darauf zu zeigen, wobei l2 auf der ursprünglichen Liste angezeigt wird.

(In der += Version Sie auch l1 rebinding wurden, ist es nur, dass in diesem Fall, dass Sie es auf die gleichen list rebinding wurden bereits gebunden war, so kann man in der Regel, dass ein Teil ignorieren.)

+0

heißt '__iadd__' eigentlich' __add__' im Falle eines 'AttributError'? – mgilson

+0

Nun, 'i .__ iadd__' ruft nicht '__add__'; es ist 'i + = 1', das' __add__' aufruft. – abarnert

+0

errr ... Ja, das habe ich gemeint. Interessant. Ich habe nicht bemerkt, dass das automatisch gemacht wurde. – mgilson

2

Wenn Sie haben nur mit Literalen zu tun, dann i += 1 hat das gleiche Verhalten wie i = i + 1.

+19

Haben Sie das Gefühl, dass die vorhandenen Antworten diese Tatsache nicht ausreichend verdeutlichen? Dies scheint eine unnötige Antwort angesichts der umfangreichen Antwort von Mgilson. – Guvante

+5

Ja, weil keine der vorhandenen Antworten nur die eigentliche Frage ansprach, gingen sie in alle möglichen Richtungen, während ich die Frage als eine einfachere unter Einbeziehung von Literalen interpretierte. –

+2

In Python hat der Begriff "Literal" zwei verschiedene Bedeutungen, von denen Sie wahrscheinlich beide nicht kennen. Es gibt Literal-Token (die 'None',' -1' oder 'a''b'' nicht enthalten) oder Literal-Strukturen (die List/Tuple/Dict-Anzeigen enthalten). Auf der Ebene der Literalstrukturen hat 'i + = [1]' nicht das gleiche Verhalten wie 'i = i + [1]', obwohl es sich um ein Listenanzeigeliteral handelt. – abarnert

5

Hier ist ein Beispiel, das direkt i += x mit i = i + x vergleicht:

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] 
-2

In einfachen Worten, Sie haben zwei Fälle:

i = i + 1 

Dieses eine neue Variable erstellt i mit dem Wert der vorherigen i und erhöht es um eins und speichert es an einem anderen Speicherort.

i += 1 

Dies erzeugt keine neue Variable, sondern erhöht die Variable i an derselben Speicherstelle. Dies ist viel effizienter als das vorherige.

Verwandte Themen