Uwaga: używanie operacji w miejscu na tablicach NumPy, które współużytkują pamięć, nie stanowi już problemu w wersji 1.13.0 i nowszych (zobacz szczegóły tutaj ). Te dwie operacje dadzą ten sam wynik. Ta odpowiedź dotyczy tylko wcześniejszych wersji NumPy.
Mutowanie tablic, gdy są używane w obliczeniach, może prowadzić do nieoczekiwanych wyników!
W przykładzie w pytaniu odejmowanie za pomocą -=
modyfikuje drugi element programu, a
a następnie natychmiast używa tego zmodyfikowanego drugiego elementu w operacji na trzecim elemencie programu a
.
Oto, co dzieje się a[1:] -= a[:-1]
krok po kroku:
a
to tablica z danymi [1, 2, 3]
.
Mamy dwa poglądy na te dane: a[1:]
jest [2, 3]
i a[:-1]
jest [1, 2]
.
Rozpoczyna się odejmowanie w miejscu -=
. Pierwszy element a[:-1]
1 jest odejmowany od pierwszego elementu a[1:]
. To zostało zmienione a
na [1, 1, 3]
. Teraz mamy a[1:]
to widok danych [1, 3]
, a a[:-1]
jest to widok danych [1, 1]
(drugi element tablicy a
został zmieniony).
a[:-1]
jest teraz [1, 1]
i NumPy musi teraz odjąć drugi element, który wynosi 1 (już nie 2!) od drugiego elementu a[1:]
. To daje a[1:]
pogląd na wartości [1, 2]
.
a
jest teraz tablicą zawierającą wartości [1, 1, 2]
.
b[1:] = b[1:] - b[:-1]
nie ma tego problemu, ponieważ najpierw b[1:] - b[:-1]
tworzy nową tablicę, a następnie przypisuje wartości w tej tablicy do b[1:]
. Nie modyfikuje b
się podczas odejmowania, więc widoki b[1:]
i b[:-1]
nie zmieniają się.
Ogólna rada jest taka, aby unikać modyfikowania jednego widoku w miejscu innym, jeśli się pokrywają. Obejmuje operatorów -=
, *=
itp i za pomocą out
parametru w funkcji uniwersalnych (jak np.subtract
i np.multiply
), aby odpisać na jednej z tablic.
Wewnętrzna różnica polega na tym, że:
a[1:] -= a[:-1]
jest równoważne z tym:
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)))
kiedy to:
b[1:] = b[1:] - b[:-1]
mapuje do tego:
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)))
W niektórych przypadkach,
__sub__()
i__isub__()
pracują w podobny sposób. Ale zmienne obiekty powinny mutować i zwracać się podczas używania__isub__()
, podczas gdy powinny zwracać nowy obiekt z__sub__()
.Wykonywanie operacji na plasterkach na obiektach numpy tworzy na nich widoki, więc ich użycie daje bezpośredni dostęp do pamięci „oryginalnego” obiektu.
źródło
Doktorzy mówią:
Z reguły odejmowanie powiększone (
x-=y
) jest możliwex.__isub__(y)
dla operacji IN- miejsce JEŻELI możliwe, gdy jest możliwe normalne odejmowanie (x = x-y
)x=x.__sub__(y)
. W przypadku obiektów niemodyfikowalnych, takich jak liczby całkowite, jest to równoważne. Ale w przypadku zmiennych, takich jak tablice lub listy, jak w twoim przykładzie, mogą to być bardzo różne rzeczy.źródło