2013-04-16 43 views
13

Câu hỏi cơ bản là: Điều gì xảy ra dưới mui xe khi thực hiện: a[i] += b?Hoạt động tại chỗ của numpy (ví dụ: `+ =`) hoạt động như thế nào?

Do sau:

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

Tôi hiểu rằng:

  • a[i] = x cũng giống như a.__setitem__(i, x), mà gán trực tiếp đến các mục được chỉ định bởi i
  • a += x được giống như a.__iadd__(x) bổ sung tại chỗ

Nhưng những gì xảy ra khi tôi làm:

a[i] += x 

Cụ thể:

  1. Đây có phải là giống như a[i] = a[i] + x? (Mà không phải là một hoạt động tại chỗ)
  2. Liệu nó tạo sự khác biệt trong trường hợp này nếu i là:
    • một chỉ số int, hoặc
    • một ndarray, hoặc
    • một đối tượng slice

nền

Lý do tôi bắt đầu đào sâu vào việc này là tôi gặp phải một hành vi phi trực quan khi làm việc với các chỉ số trùng lặp:

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.]) 

thứ Thú vị hơn về chỉ số trùng lặp trong this question.

+0

Tôi không thấy sự cố với ví dụ nền của bạn. Nó rõ ràng cần phải lặp nội bộ trên tất cả các giá trị. Nếu bạn chỉ định các giá trị cho một mảng như là một hoạt động tại chỗ, trong đó tất cả các chỉ số đều giống nhau, thì giá trị cuối cùng sẽ là phần còn lại. Điều này rõ ràng những gì xảy ra (giá trị cuối cùng của 'x' clobbers tất cả phần còn lại). –

+1

Ồ, tôi thấy vấn đề. Nếu nội bộ bạn đang thực sự viết lại cho bộ nhớ kết quả của hoạt động, bạn mong đợi để xem tổng của tất cả các giá trị trong 'x'. hmmm ... –

+0

@HenryGomersall, chính xác. – shx2

Trả lời

11

Điều đầu tiên bạn cần nhận ra là a += x không ánh xạ chính xác đến a.__iadd__(x), thay vào đó ánh xạ tới a = a.__iadd__(x).Lưu ý rằng documentation đặc biệt nói rằng các toán tử tại chỗ trả về kết quả của chúng và điều này không phải là self (mặc dù trong thực tế, nó thường là). Điều này có nghĩa a[i] += x bản đồ trivially để:

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

Vì vậy, việc bổ sung kỹ thuật xảy ra tại chỗ, nhưng chỉ trên một đối tượng tạm thời. Tuy nhiên, vẫn có khả năng một đối tượng tạm thời ít được tạo hơn nếu nó được gọi là __add__.

+1

Nice, thanx cho tip! Có nguy hiểm nào khi chạy một .__ iadd __ (a)? – ntg

4

Thực ra không có gì liên quan đến việc lờ mờ. Không có "set/getitem tại chỗ" trong python, những thứ này tương đương với a[indices] = a[indices] + x. Biết rằng, nó trở nên khá rõ ràng những gì đang xảy ra. (EDIT: Như lvc viết, thực sự là bên tay phải là tại chỗ, do đó nó là a[indices] = (a[indices] += x) nếu đó là cú pháp pháp, mà có tác dụng tương tự mặc dù)

Tất nhiên a += x thực sự là tại chỗ, bằng ánh xạ a đến đối số np.addout.

Điều này đã được thảo luận trước đây và không được phép làm gì cả. Mặc dù có ý tưởng để có một số np.add.at(array, index_expression, x) ít nhất cho phép các hoạt động đó.

+2

giảm ufuncs đã được thêm vào numpy 1.8.0 – jtaylor

2

Như Ivc giải thích, không có phương pháp thêm mục tại chỗ, do đó, dưới mui xe, nó sử dụng __getitem__, sau đó __iadd__, sau đó __setitem__. Dưới đây là một cách để thực nghiệm quan sát hành vi:

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 

It in

about to increment a[0] 
getitem 
iadd 
setitem 
2

Tôi nghĩ rằng sự khác biệt lớn ở đây là ở chỗ các nhà khai thác có thể trở lại tham chiếu tương tự, nhưng hiệu quả là khác nhau trong NumPy so với Python.

Bắt đầu với Python

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

Đây là những tài liệu tham khảo tương tự.

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

Thêm vào chỗ tạo tham chiếu mới.

Bây giờ cho NumPy

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

Một lần nữa đây là những tài liệu tham khảo tương tự, nhưng ở vị trí nhà khai thác có ảnh hưởng khác nhau.

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

Thêm vào đó một bản cập nhật tham chiếu. Điều này không giống như gọi số numpy.add tạo bản sao trong tham chiếu mới.

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

Trong chỗ hoạt động trên tài liệu tham khảo mượn

Sự nguy hiểm ở đây là nếu tham chiếu sẽ được chuyển cho một phạm vi khác nhau.

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

Các tài liệu tham khảo luận để x được chuyển vào phạm vi f mà không tạo một bản sao và trong thực tế thay đổi giá trị ở tham chiếu và vượt qua nó trở lại.

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

Điều này có thể gây nhầm lẫn nên chỉ sử dụng toán tử tại chỗ trên các tham chiếu thuộc phạm vi hiện tại và cẩn thận về tham chiếu được mượn.

Các vấn đề liên quan