it-swarm.com.de

Was ist der Unterschied zwischen i = i + 1 und i + = 1 in einer 'for'-Schleife?

Ich fand heute eine merkwürdige Sache heraus und fragte mich, ob jemand etwas Licht in den Unterschied bringen könnte?

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 dem Ausführen jeder for -Schleife wurde A nicht geändert, aber B wurde jedem Element eine hinzugefügt. Ich benutze tatsächlich die B -Version, um in ein initialisiertes NumPy-Array innerhalb einer for-Schleife zu schreiben.

105
Adam Fjeldsted

Der Unterschied besteht darin, dass einer die Datenstruktur selbst modifiziert (In-Place-Operation) b += 1, während der andere nur die Variable a = a + 1 neu zuordnet .


Nur der Vollständigkeit halber:

x += y führt nicht immer eine In-Place-Operation durch, es gibt (mindestens) drei Ausnahmen:

  • Wenn x keine __iadd__-Methode implementiert , ist die x += y-Anweisung nur eine Abkürzung für x = x + y. Dies wäre der Fall, wenn x so etwas wie ein int wäre.

  • Wenn __iadd__NotImplemented zurückgibt, wird Python auf x = x + y zurückgesetzt.

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

Zufällig sind Ihre bs numpy.ndarrays, das __iadd__ implementiert, und geben sich selbst zurück, sodass Ihre zweite Schleife das ursprüngliche Array an Ort und Stelle ändert.

Mehr dazu erfahren Sie in der Python-Dokumentation von "Numerische Typen emulieren" .

Diese [__i*__] Methoden werden aufgerufen, um die erweiterten arithmetischen Zuweisungen (+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=) zu implementieren. Diese Methoden sollten versuchen, die Operation direkt auszuführen (self zu ändern) und das Ergebnis (das self sein kann, aber nicht muss) zurückgeben. Wenn eine bestimmte Methode nicht definiert ist, wird die erweiterte Zuweisung auf die normalen Methoden zurückgesetzt. Wenn x beispielsweise eine Instanz einer Klasse mit der Methode __iadd__() ist, entspricht x += yx = x.__iadd__(y). Ansonsten werden x.__add__(y) und y.__radd__(x) wie bei der Auswertung von x + y berücksichtigt. In bestimmten Situationen kann eine erweiterte Zuweisung zu unerwarteten Fehlern führen (siehe Warum löst a_Tuple[i] += ["item"] eine Ausnahme aus, wenn der Zusatz funktioniert? ), aber dieses Verhalten ist tatsächlich Teil des Datenmodells.

110
MSeifert

Im ersten Beispiel weisen Sie die Variable a neu zu, während Sie im zweiten Beispiel die Daten mit dem Operator _+=_ direkt ändern.

Siehe den Abschnitt über 7.2.1. Augmented Assignment Statements :

Ein erweiterter Zuweisungsausdruck wie _x += 1_ kann in _x = x + 1_ umgeschrieben werden, um einen ähnlichen, aber nicht genau gleichen Effekt zu erzielen. In der erweiterten Version wird x nur einmal ausgewertet. Außerdem wird, wenn möglich, die eigentliche Operation direkt ausgeführt , was bedeutet, dass kein neues Objekt erstellt und das alte Objekt dem Ziel zugewiesen wird wird stattdessen geändert.

_+=_ Operator ruft __iadd__ auf. Mit dieser Funktion wird die Änderung übernommen, und das Ergebnis wird erst nach ihrer Ausführung auf das Objekt zurückgesetzt, auf das Sie _+=_ "anwenden".

__add__ hingegen übernimmt die Parameter und gibt ihre Summe zurück (ohne sie zu ändern).

28
Maroun

Wie bereits erwähnt, aktualisiert b += 1b an Ort und Stelle, während a = a + 1a + 1 berechnet und dem Ergebnis dann den Namen a zuweist (jetzt bezieht sich a nicht mehr auf eine Reihe von A).

Um den Operator += richtig zu verstehen, müssen wir auch das Konzept von veränderlichen und unveränderlichen Objekten verstehen. Überlegen Sie, was passiert, wenn wir den .reshape weglassen:

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 nicht aktualisiert wurde, was bedeutet, dass c += 1 und c = c + 1 gleichwertig sind. Dies liegt daran, dass C jetzt ein 1D-Array (C.ndim == 1) ist. Wenn Sie also über C iterieren, wird jedes ganzzahlige Element herausgezogen und c zugewiesen.

Jetzt in Python sind Ganzzahlen unveränderlich, was bedeutet, dass direkte Aktualisierungen nicht zulässig sind. Dadurch wird c += 1 in c = c + 1 umgewandelt, wobei c jetzt auf eine neue Ganzzahl verweist, die nicht mit C in gekoppelt ist sowieso. Wenn Sie die umgeformten Arrays durchlaufen, werden b (und a) jeweils ganze Zeilen (np.ndarray) zugewiesen. Dies sind veränderbare Objekte, dh, Sie dürfen bleiben neue ganze Zahlen nach Belieben, was passiert, wenn Sie a += 1 tun.

Es sollte erwähnt werden, dass, obwohl + und += wie oben beschrieben in Beziehung stehen sollen (und dies in der Regel auch sind), jeder Typ sie nach Belieben implementieren kann, indem er die Methoden __add__ und __iadd__ definiert .

13
jmd_dk

Die Kurzform (a += 1) bietet die Möglichkeit, a direkt zu ändern, anstatt ein neues Objekt zu erstellen, das die Summe darstellt, und es erneut mit demselben Namen zu verknüpfen (a = a + 1) , Die Kurzform (a += 1) ist sehr effizient, da im Gegensatz zu a = a + 1 nicht unbedingt eine Kopie von a erstellt werden muss.

Beachten Sie auch, dass sie sich unterscheiden, wenn sie dasselbe Ergebnis ausgeben, da sie separate Operatoren sind: + und +=

4
Inconnu

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. Dadurch wird ein neues Objekt erstellt, und daher wird A nicht geändert. Danach wird die Variable a auf das Ergebnis gesetzt.

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

3
Andi Kleve

Ein zentrales Problem hierbei ist, dass diese Schleife über die Zeilen (1. Dimension) von B iteriert:

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]

Somit wirkt der += auf ein veränderliches Objekt, ein Array.

Dies ist in den anderen Antworten impliziert, kann jedoch leicht übersehen werden, wenn Sie sich auf die Neuzuweisung von a = a+1 konzentrieren.

Ich könnte auch eine direkte Änderung an b vornehmen, indem ich [:] indexiere, oder sogar etwas ausgefalleneres, b[1:]=0:

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 müssen wir bei einem 2D-Array wie B normalerweise nicht in den Zeilen iterieren. Viele Operationen, die mit einem einzigen von B funktionieren, funktionieren auch mit dem Ganzen. B += 1, B[1:] = 0 usw.

2
hpaulj