2017-01-03 2 views
97

Ich habe heute eine merkwürdige Sache herausgefunden und mich gefragt, ob jemand etwas Licht in den Unterschied bringen könnte.Was ist der Unterschied zwischen i = i + 1 und i + = 1 in einer 'for' Schleife?

import numpy as np 

A = np.arange(12).reshape(4,3) 
for a in A: 
    a = a + 1 

B = np.arange(12).reshape(4,3) 
for b in B: 
    b += 1 

Nach jeder for Schleife läuft, hat A nicht verändert, aber B hatte einen zu jedem Element hinzugefügt. Ich benutze tatsächlich die B-Version, um auf ein initialisiertes NumPy-Array innerhalb einer for-Schleife zu schreiben.

+12

'i = i + 1 '' I' neu zuweist, 'i + = 1' Schritten' I' von '1' – juniorRubyist

+4

Haben Sie feststellen, dass Sie in den Zeilen des Arrays iterieren, nicht die einzelnen Elemente? – hpaulj

+0

auf der ganzen Linie Ich + = 1 verhindert eine Anweisung in Assembly Sprachlevel –

Antwort

104

Der Unterschied besteht darin, dass man die Daten-Struktur selbst modifiziert (in-place-Betrieb) b += 1 während der andere gerade neu zuweist der Variable a = a + 1.


Nur der Vollständigkeit halber:

x += y ist nicht immer ein In-Place-Operation zu tun, gibt es (mindestens) drei Ausnahmen:

  • Wenn xnicht der Fall ist implementieren eine __iadd__ Methode dann die x += y Anweisung ist nur eine Kurzschrift für x = x + y. Dies wäre der Fall, wenn x so etwas wie ein int wäre.

  • Wenn __iadd__NotImplemented zurückgibt, fällt Python zurück auf x = x + y.

  • Die Methode __iadd__ könnte theoretisch so implementiert werden, dass sie nicht funktioniert. Es wäre wirklich komisch das zu tun.

wie es Ihre b s passiert, sind numpy.ndarray s die __iadd__ implementiert und Rückkehr selbst so Ihre zweite Schleife in-place das ursprüngliche Array modifiziert.

Sie können mehr darüber in der Python documentation of "Emulating Numeric Types" lesen.

Diese [__i*__] Methoden werden aufgerufen, um die erweiterten arithmetischen Zuweisungen (+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=) zu implementieren. Diese Methoden sollten versuchen, die Operation an Ort und Stelle durchzuführen (das Selbst zu modifizieren) und das Ergebnis zurückgeben (das könnte sein, muss aber nicht selbst sein). Wenn eine bestimmte Methode nicht definiert ist, greift die erweiterte Zuordnung auf die normalen Methoden zurück. Wenn beispielsweise x eine Instanz einer Klasse mit einer __iadd__()-Methode ist, entspricht x += yx = x.__iadd__(y). Ansonsten werden x.__add__(y) und y.__radd__(x) berücksichtigt, ebenso wie bei der Auswertung x + y. In bestimmten Situationen kann die erweiterte Zuweisung zu unerwarteten Fehlern führen (siehe Why does a_tuple[i] += ["item"] raise an exception when the addition works?). Dieses Verhalten ist jedoch Teil des Datenmodells.

+2

Piggybacking der Top-Kommentar für eine weniger technische Erklärung: "a = a + 1" hier ist funktionell äquivalent zu "Dummy = a + 1", da die "a" auf der linken Seite des Gleichheitszeichens nicht mehr zugewiesen wird ein Blick auf 'A'. Da '+ =' nicht wie '=' neu zuweist, sondern stattdessen 'an Ort und Stelle' ändert, bleibt 'b' eine Ansicht von 'B' und somit' B' Inkrementen, wo 'A' nicht ist. –

+0

Es gibt eine sehr wichtige Überlegung, besonders wenn man numpy benutzt. Bei Verwendung von add wird der Datentyp der ursprünglichen Variablen beibehalten. Wenn Sie also zum Beispiel versuchen, einem reellwertigen numpy Array einen komplexen Wert hinzuzufügen, bleibt das in-Place-Add real value, während Sie bei Verwendung von reassignment das korrekte komplexe Ergebnis erhalten. Seien Sie vorsichtig, wenn Sie an Ort und Stelle mit numpy hinzufügen. – nevsan

+0

@nevsan In diesem Fall wird ein 'TypeError' ausgelöst, so dass es zwar nicht wie beabsichtigt funktioniert, aber _silently_ nicht fehlschlägt oder das falsche tut, wie es in Ihrem Kommentar heißt. – MSeifert

28

Im ersten Beispiel wird die Neuzuweisung der Variable a, während in dem zweiten, um die Daten an Ort und Stelle ist, modifiziert, den += Operator.

finden Sie im Abschnitt über 7.2.1. Augmented assignment statements :

Ein Zuweisungsausdruck als x = x + 1 werden kann neu geschrieben wie x += 1 ergänzte ein ähnliches zu erreichen, aber nicht genau die gleiche Wirkung. In der erweiterten Version wird x nur einmal ausgewertet. Wenn es möglich ist, wird auch der eigentliche Vorgang vor Ort ausgeführt, was bedeutet, dass anstatt ein neues Objekt zu erstellen und dem Ziel zuzuweisen, stattdessen das alte Objekt geändert wird.

+= Operator ruft __iadd__. Diese Funktion führt die Änderung vor Ort durch, und erst nach ihrer Ausführung wird das Ergebnis auf das Objekt zurückgesetzt, auf das Sie die += an "anwenden".

__add__ auf der anderen Seite nimmt die Parameter und gibt ihre Summe zurück (ohne sie zu ändern).

5

Die Kurzform (a += 1) die Möglichkeit hat, in-place a zu modifizieren, anstatt ein neues Objekt zu schaffen, die Summe darstellt, und es zurück zu dem gleichen Namen rebinding (a = a + 1) .so die kurze Form (a += 1) ist sehr effizient, da es nicht notwendigerweise eine Kopie von a im Gegensatz zu a = a + 1 machen muss.

auch, selbst wenn sie das gleiche Ergebnis ausgeben, bemerken sie anders sind, weil sie getrennte Operatoren sind: + und +=

13

Wie bereits erwähnt, b += 1 Updates b an Ort und Stelle, während a = a + 1a + 1 und dann Abtretungsempfänger berechnet der Name a zum Ergebnis (jetzt a verweist nicht mehr auf eine Zeile A).

Um die += Bediener richtig obwohl zu verstehen, brauchen wir das Konzept der wandelbar gegen unveränderlichen Objekte zu verstehen, auch. Überlegen Sie, was passiert, wenn wir die .reshape auslassen:

C = np.arange(12) 
for c in C: 
    c += 1 
print(C) # [ 0 1 2 3 4 5 6 7 8 9 10 11] 

Wir sehen, dass C ist nicht aktualisiert, was bedeutet, dass c += 1 und c = c + 1 gleichwertig sind. Dies liegt daran, dass C nun ein 1D-Array ist (C.ndim == 1), und daher beim Iterieren über C jedes Integer-Element herausgezogen und einem c zugewiesen wird.

nun in Python, ganze Zahlen sind unveränderlich, was bedeutet, dass effektiv in-Place-Updates sind nicht erlaubt, c += 1 in c = c + 1 verwandeln, wo c bezieht sich nun auf eine ganze Zahl neuen, nicht gekoppelt C in keiner Weise.Wenn Sie die umgeformten Arrays durchlaufen, werden ganze Zeilen (np.ndarray s) gleichzeitig b (und a) zugewiesen, die veränderbare Objekte sind, was bedeutet, dass Sie nach Belieben neue Ganzzahlen einfügen können tun a += 1.

Es sollte erwähnt werden, dass, obwohl + und += ment sind, wie oben beschrieben bezogen werden (und sehr viel in der Regel sind), können sie jede Art implementieren eine Möglichkeit, es durch die Definition der __add__ und __iadd__ Methoden will, respectively.

4

Zunächst einmal: Die Variablen a und b in den Schleifen beziehen sich auf numpy.ndarray Objekte.

In der ersten Schleife wird a = a + 1 wie folgt ausgewertet: die __add__(self, other) Funktion von numpy.ndarray wird aufgerufen. Dies erzeugt ein neues Objekt und daher wird A nicht modifiziert. Danach wird die Variable a auf das Ergebnis gesetzt.

In der zweiten Schleife wird kein neues Objekt erstellt. Die Anweisung b += 1 ruft die __iadd__(self, other)-Funktion von numpy.ndarray auf, die das Objekt ndarray ändert, auf das sich b bezieht. Daher wird B geändert.

3

Ein Schlüsselproblem hierbei ist, dass diese Schleife iteriert über die Reihen (1. Dimension) von B:

In [258]: B 
Out[258]: 
array([[ 0, 1, 2], 
     [ 3, 4, 5], 
     [ 6, 7, 8], 
     [ 9, 10, 11]]) 
In [259]: for b in B: 
    ...:  print(b,'=>',end='') 
    ...:  b += 1 
    ...:  print(b) 
    ...:  
[0 1 2] =>[1 2 3] 
[3 4 5] =>[4 5 6] 
[6 7 8] =>[7 8 9] 
[ 9 10 11] =>[10 11 12] 

So ist die += auf einem veränderliches Objekt handelt, ein Array.

Dies ist in den anderen Antworten impliziert, aber leicht verpasst, wenn Sie sich auf die a = a+1 Neuzuweisung konzentrieren.

ich auch eine direkte Änderung b mit [:] Indizierung, oder sogar etwas schicker, b[1:]=0 machen könnte:

In [260]: for b in B: 
    ...:  print(b,'=>',end='') 
    ...:  b[:] = b * 2 

[1 2 3] =>[2 4 6] 
[4 5 6] =>[ 8 10 12] 
[7 8 9] =>[14 16 18] 
[10 11 12] =>[20 22 24] 

Natürlich mit einem 2D-Array wie B wir in der Regel müssen auf nicht iterieren die Reihen. Viele Operationen, die an einem einzigen von B arbeiten, arbeiten auch an der ganzen Sache. B += 1, B[1:] = 0 usw.

Verwandte Themen