Unterschied zwischen a - = b und a = a - b in Python

89

Ich habe diese Lösung kürzlich angewendet , um alle N Matrixzeilen zu mitteln. Obwohl die Lösung im Allgemeinen funktioniert, hatte ich Probleme bei der Anwendung auf ein 7x1-Array. Ich habe festgestellt, dass das Problem bei der Verwendung des -=Operators liegt. Um ein kleines Beispiel zu machen:

import numpy as np

a = np.array([1,2,3])
b = np.copy(a)

a[1:] -= a[:-1]
b[1:] = b[1:] - b[:-1]

print a
print b

welche Ausgänge:

[1 1 2]
[1 1 1]

Im Fall eines Arrays a -= bergibt sich also ein anderes Ergebnis als a = a - b. Ich dachte bis jetzt, dass diese beiden Wege genau gleich sind. Was ist der Unterschied?

Wie kommt es, dass die von mir erwähnte Methode zum Summieren aller N Zeilen in einer Matrix funktioniert, z. B. für eine 7x4-Matrix, aber nicht für ein 7x1-Array?

iasonas
quelle

Antworten:

80

Hinweis: Die Verwendung von In-Place-Vorgängen für NumPy-Arrays, die Speicher gemeinsam nutzen, ist ab Version 1.13.0 kein Problem mehr (siehe Details hier ). Die beiden Operationen führen zum gleichen Ergebnis. Diese Antwort gilt nur für frühere Versionen von NumPy.


Das Mutieren von Arrays während der Verwendung in Berechnungen kann zu unerwarteten Ergebnissen führen!

In dem Beispiel in der Frage -=modifiziert die Subtraktion mit das zweite Element von aund verwendet dann sofort das modifizierte zweite Element in der Operation für das dritte Element von a.

Folgendes passiert a[1:] -= a[:-1]Schritt für Schritt:

  • aist das Array mit den Daten [1, 2, 3].

  • Wir haben zwei Ansichten zu diesen Daten: a[1:]ist [2, 3]und a[:-1]ist [1, 2].

  • Die In-Place-Subtraktion -=beginnt. Das erste Element von a[:-1]1 wird vom ersten Element von subtrahiert a[1:]. Dies wurde geändert a, um zu sein [1, 1, 3]. Jetzt haben wir a[1:]eine Ansicht der Daten [1, 3]und a[:-1]eine Ansicht der Daten [1, 1](das zweite Element des Arrays awurde geändert).

  • a[:-1]ist jetzt [1, 1]und NumPy muss jetzt sein zweites Element, das 1 (nicht mehr 2!) ist, vom zweiten Element von subtrahieren a[1:]. Dies macht a[1:]eine Ansicht der Werte [1, 2].

  • aist jetzt ein Array mit den Werten [1, 1, 2].

b[1:] = b[1:] - b[:-1]hat dieses Problem nicht, da zuerst b[1:] - b[:-1]ein neues Array erstellt und dann die Werte in diesem Array zugewiesen werden b[1:]. Es ändert sich nicht bwährend der Subtraktion, so dass sich die Ansichten b[1:]und b[:-1]nicht ändern.


Der allgemeine Rat ist, zu vermeiden, dass eine Ansicht anstelle einer anderen geändert wird, wenn sie sich überschneiden. Dazu gehört auch die Betreiber -=, *=etc. und mit Hilfe der outParameter in universellen Funktionen (wie np.subtractund np.multiply) auf eines des Arrays schreiben zurück.

Alex Riley
quelle
4
Ich bevorzuge diese Antwort mehr als die derzeit akzeptierte. Es wird eine sehr klare Sprache verwendet, um den Effekt des Änderns von veränderlichen Objekten an Ort und Stelle zu zeigen. Noch wichtiger ist, dass im letzten Absatz direkt die Bedeutung der direkten Änderung für überlappende Ansichten hervorgehoben wird. Dies sollte die Lehre sein, die Sie aus dieser Frage ziehen sollten.
Reti43
43

Intern besteht der Unterschied darin, dass dies:

a[1:] -= a[:-1]

ist gleichbedeutend damit:

a[1:] = a[1:].__isub__(a[:-1])
a.__setitem__(slice(1, None, None), a.__getitem__(slice(1, None, None)).__isub__(a.__getitem__(slice(1, None, None)))

während dies:

b[1:] = b[1:] - b[:-1]

Karten dazu:

b[1:] = b[1:].__sub__(b[:-1])
b.__setitem__(slice(1, None, None), b.__getitem__(slice(1, None, None)).__sub__(b.__getitem__(slice(1, None, None)))

In einigen Fällen __sub__()und __isub__()arbeiten auf ähnliche Weise. Aber veränderbare Objekte sollten mutieren und sich bei der Verwendung selbst zurückgeben __isub__(), während sie ein neues Objekt mit zurückgeben sollten __sub__().

Durch Anwenden von Slice-Operationen auf numpy-Objekte werden Ansichten auf diese erstellt, sodass durch deren Verwendung direkt auf den Speicher des "ursprünglichen" Objekts zugegriffen wird.

glglgl
quelle
11

Die Dokumente sagen:

Die Idee hinter der erweiterten Zuweisung in Python ist, dass es nicht nur eine einfachere Möglichkeit ist, die übliche Praxis des Speicherns des Ergebnisses einer Binäroperation in ihrem linken Operanden zu schreiben, sondern auch eine Möglichkeit für den betreffenden linken Operanden wissen, dass es "auf sich selbst" arbeiten sollte, anstatt eine modifizierte Kopie von sich selbst zu erstellen.

Als Faustregel gilt, dass eine erweiterte Substraktion ( x-=y) x.__isub__(y)für die IN- Place-Operation WENN möglich ist, wenn eine normale Substraktion ( x = x-y) vorliegt x=x.__sub__(y). Bei nicht veränderlichen Objekten wie Ganzzahlen ist dies äquivalent. Aber für veränderbare wie Arrays oder Listen, wie in Ihrem Beispiel, können sie sehr unterschiedliche Dinge sein.

BM
quelle