2013-04-16 15 views
13

Podstawowe pytanie brzmi: Co dzieje się pod maską: a[i] += b?Jak działają operacje numpy na miejscu (np. `+ =`)?

Biorąc pod uwagę następujące elementy:

import numpy as np 
a = np.arange(4) 
i = a > 0 
i 
= array([False, True, True, True], dtype=bool) 

Rozumiem, że:

  • a[i] = x jest taka sama jak a.__setitem__(i, x), której przypisuje się bezpośrednio do elementów wskazanych przez i
  • a += x jest taka sama jak a.__iadd__(x) , który uzupełnia dodatek

Ale co się dzieje, gdy robię:

a[i] += x 

Konkretnie:

  1. Jest to taka sama jak a[i] = a[i] + x? (Co nie jest praca na miejscu)
  2. Czy to robi różnicę w tym przypadku, jeśli i jest:
    • indeks int lub
    • ndarray lub
    • slice obiekt

Tło

Powodem zacząłem zagłębiając się to, że natknąłem się nieintuicyjne zachowanie podczas pracy z duplikatów indeksów:

a = np.zeros(4) 
x = np.arange(4) 
indices = np.zeros(4,dtype=np.int) # duplicate indices 
a[indices] += x 
a 
= array([ 3., 0., 0., 0.]) 

bardziej interesujących rzeczy o zduplikowanych indeksów w this question.

+0

Nie widzę problemu z przykładem tła. Oczywiście musi wewnętrznie iterować we wszystkich wartościach. Jeśli przypiszesz wartości do tablicy jako operację w miejscu, w której wszystkie indeksy są takie same, ostatnia z nich będzie odrywać resztę. To wyraźnie się dzieje (ostatnia wartość 'x' powoduje, że cała reszta jest pusta). –

+1

Och, widzę problem. Jeśli wewnętrznie wracasz do pamięci wyniku operacji, możesz się spodziewać sumy wszystkich wartości w 'x'. hmmm ... –

+0

@HenryGomersall, dokładnie. – shx2

Odpowiedz

11

Najpierw należy sobie zdać sprawę, że a += x nie odwzorowuje dokładnie na a.__iadd__(x), zamiast tego mapuje na a = a.__iadd__(x).Zauważ, że documentation wyraźnie mówi, że operatorzy w miejscu zwracają swój wynik, a to nie musi być self (chociaż w praktyce zwykle tak jest). Oznacza to a[i] += x trywialnie map do:

a.__setitem__(i, a.__getitem__(i).__iadd__(x)) 

Więc dodanie technicznie dzieje się w miejscu, ale tylko na czas obiektu. Pozostaje jednak potencjalnie jeden mniej tymczasowy obiekt niż gdyby nazywał się __add__.

+1

Niezły, za wskazówki! Czy istnieje jakiekolwiek niebezpieczeństwo związane z uruchomieniem .__ iadd __ (a)? – ntg

4

Właściwie to nie ma nic wspólnego z numpy. W pythonie nie ma "set/getitem in-place", te rzeczy są równoważne a[indices] = a[indices] + x. Wiedząc o tym, staje się całkiem oczywiste, co się dzieje. (EDIT: Jak pisze LVC, faktycznie prawa strona jest na swoim miejscu, tak że jest a[indices] = (a[indices] += x) czy to składnia prawny, który ma largly ten sam efekt, chociaż)

Oczywiście a += x rzeczywiście znajduje się w miejscu, przez mapowanie a do argumentu np.addout.

Zostało to omówione wcześniej i numpy nie może nic z tym zrobić. Chociaż istnieje pomysł, aby mieć np.add.at(array, index_expression, x) przynajmniej pozwolić na takie operacje.

+2

przy zmniejszeniu liczby ufuncs został dodany do numpy 1.8.0 – jtaylor

2

Jak wyjaśnia Ivc, nie istnieje lokalna metoda dodawania pozycji, więc pod maską używa ona __getitem__, następnie __iadd__, następnie __setitem__. Oto sposób empirycznie zaobserwować to zachowanie:

import numpy 

class A(numpy.ndarray): 
    def __getitem__(self, *args, **kwargs): 
     print "getitem" 
     return numpy.ndarray.__getitem__(self, *args, **kwargs) 
    def __setitem__(self, *args, **kwargs): 
     print "setitem" 
     return numpy.ndarray.__setitem__(self, *args, **kwargs) 
    def __iadd__(self, *args, **kwargs): 
     print "iadd" 
     return numpy.ndarray.__iadd__(self, *args, **kwargs) 

a = A([1,2,3]) 
print "about to increment a[0]" 
a[0] += 1 

drukuje

about to increment a[0] 
getitem 
iadd 
setitem 
2

myślę Główną różnicą jest to, że w miejscu operatorzy mogą wrócić tego samego odniesienia, ale efekt jest inny w NumPy niż Python.

Zacznij Pythonie

>>> a = 1 
>>> b = a 
>>> a is b 
True 

Są to te same odniesienia.

>>> a += 4 
>>> a 
5 
>>> b 
1 

W miejscu dodania tworzy nowe odniesienie.

Teraz NumPy

>>> import numpy as np 
>>> a = np.array([1, 2, 3], float) 
>>> b = a 
>>> a is b 
True 

Znowu to samo odniesienie, ale w miejscu, operatorzy mają inny efekt.

>>> a += 4 
>>> a 
array([ 5., 6., 7.]) 
>>> b 
array([ 5., 6., 7.]) 

W miejscu dodanie ndarray aktualizuje odniesienie. To nie jest to samo co wywołanie numpy.add, które tworzy kopię w nowym odwołaniu.

>>> a = a + 4 
>>> a 
array([ 9., 10., 11.]) 
>>> b 
array([ 5., 6., 7.]) 

W miejscu operacje na pożyczonych odniesień

Niebezpieczeństwo tutaj jest, jeśli odwołanie jest przekazywana do innego zakresu.

>>> def f(x): 
...  x += 4 
...  return x 

Odniesienie argument x jest przekazywana do zakresu f który nie robi kopię i faktycznie zmienia wartość w tym odniesienia i przekazuje je z powrotem.

>>> f(a) 
array([ 13., 14., 15.]) 
>>> f(a) 
array([ 17., 18., 19.]) 
>>> f(a) 
array([ 21., 22., 23.]) 
>>> f(a) 
array([ 25., 26., 27.]) 

Może to być mylące, więc tylko używać operatorów w miejscu o referencje, które należą do obecnego zakresu i uważać pożyczonych referencje.

Powiązane problemy