2017-12-16 110 views
9

Hãy thủ quote NumPy: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#advanced-indexingTại sao hoạt động trên những gì có vẻ là một bản sao dữ liệu sửa đổi dữ liệu gốc?

lập chỉ mục nâng cao được kích hoạt khi đối tượng lựa chọn, obj, là một đối tượng phi tuple chuỗi, một ndarray (của kiểu dữ liệu số nguyên hoặc bool), hoặc một tuple với ít nhất một đối tượng chuỗi hoặc ndarray (của kiểu dữ liệu nguyên hoặc bool). Có hai loại chỉ mục nâng cao: số nguyên và Boolean.

Lập chỉ mục nâng cao luôn trả về bản sao của dữ liệu (tương phản với cắt cơ bản trả về một chế độ xem).

Sau đó, hoạt động trên những gì được trả về bằng cách lập chỉ mục nâng cao sẽ không bao giờ sửa đổi mảng ban đầu. Và quả thực:

import numpy as np 

arr = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90]) 
indexes = np.array([3, 6, 4]) 

slicedArr = arr[indexes] 
slicedArr *= 5 
arr 

này in:

array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90]) 

Tuy nhiên, điều này không phải lúc nào cũng dường như là trường hợp. Oddly nếu tôi không lưu bất cứ điều gì đã được trả lại bởi các nhà điều hành [] đến một biến trung gian tôi bằng cách nào đó hoạt động trên mảng ban đầu. Hãy xem xét ví dụ sau:

import numpy as np 

arr = np.array([0, 10, 20, 30, 40, 50, 60, 70, 80, 90]) 
indexes = np.array([3, 6, 4]) 

arr[indexes] *= 5 
arr 

in này:

array([ 0, 10, 20, 150, 200, 50, 300, 70, 80, 90]) 

Tôi không phàn nàn. Trên thực tế, đây là một cuộc sống tiết kiệm cho tôi. Tuy nhiên, tôi không hiểu tại sao điều này làm việc và tôi thực sự muốn hiểu điều này.

Để hiểu rõ nhất của tôi, ngay khi tôi viết arr[indexes] Tôi đang tạo bản sao của mảng; do đó *= 5 tiếp theo sẽ hoạt động trên bản sao này chứ không phải trên mảng ban đầu. Tuy nhiên, kết quả của phép tính này nên được loại bỏ, vì nó không được ghi vào bất kỳ biến nào.

Tuy nhiên, hiển nhiên tôi đã sai.

Tôi hiểu nhầm ở đâu?

+3

sự khác biệt là ví dụ đầu tiên của bạn đi qua '__getitem__' mà không tạo ra một bản sao bởi vì nó phải. ví dụ thứ hai của bạn không tương đương bởi vì gán cho một cái gì đó của dạng 'var [idx]' gọi 'var .__ setitem__' cho phép thực hiện gán trực tiếp cho cái gì đó không thể biểu diễn dưới dạng xem –

+1

Ví dụ thứ hai _assigns_ to' arr [ chỉ mục], trong đó ví dụ đầu tiên không. –

+3

Không giống như trong C++, 'a [b] = c' không thể được phân tách thành" lấy 'a [b]' "và" gán cho điều bạn đã truy xuất "; chỉ định chỉ mục là một hoạt động đơn lẻ khác với truy xuất chỉ mục và gán thông thường. – user2357112

Trả lời

6

Trong khi báo cáo

a = expr 

a[x] = expr 

trông giống như, họ đang thực sự cơ bản khác nhau. Đầu tiên gắn tên 'a' vào expr. Thứ hai là nhiều hơn hoặc ít hơn đến a.__setitem__(x, expr).Những gì __setitem__ thực sự là tùy thuộc vào bất cứ ai đã thực hiện nó, nhưng ngữ nghĩa thông thường là cập nhật đối tượng vùng chứa a tại vị trí được chỉ định bởi x với expr. Cụ thể, không có đối tượng trung gian "đại diện cho a[x]" được tạo.

Chỉ vì mục đích hoàn chỉnh a[x] nếu nó không ở trên l.h.s. của cú pháp giống như một bài tập có ít nhiều tương đương với a.__getitem__(x).

Cập nhật để đáp ứng với một câu hỏi tiếp theo (Điều gì xảy ra khi a[x] *= 5 được thực hiện?) Cho chúng tôi cụ các phương pháp thích hợp để chúng ta có thể nhìn thấy những gì đang xảy ra. Dưới đây, __imul__ là tại chỗ nhân "phương pháp kỳ diệu":

import numpy as np 

class spy(np.ndarray): 
    def __getitem__(self, key): 
     print('getitem', key) 
     return super().__getitem__(key) 
    def __setitem__(self, key, value): 
     print('setitem', key) 
     return super().__setitem__(key, value) 
    def __imul__(self, other): 
     print('imul', other) 
     return super().__imul__(other) 

a = spy((5, 5)) 
a[...] = 1 
a[[1,2],[4,2]] *= 5 

Prints:

setitem Ellipsis 
getitem ([1, 2], [4, 2]) 
imul 5 
setitem ([1, 2], [4, 2]) 
+0

Có thể 'a [x] * = 5' được phân tách thành' a [x] = (a [x] * 5) 'không? Do đó, 'a [x] * = 5' tương đương với' a .__ setitem __ (x, a .__ getitem __ (x) * 5) '? Tôi có đúng hay sai? – gaazkam

+0

@gaazkam gần như đúng, '* =' kiểm tra nếu 'l.h.s.' của nó có phương thức' __imul__' thực hiện phép nhân tại chỗ. Vì vậy, trung gian được trả về bởi 'a .__ getitem __ (x)' thực sự thay đổi tại chỗ trước khi nó được chuyển tới 'a .__ setitem__'. –

+0

@gaazkam Tôi đã cập nhật câu trả lời để đề cập đến điều này. –

1

Điểm mấu chốt ở đây là

nâng cao lập chỉ mục luôn lợi nhuận một bản sao của dữ liệu

Trong ví dụ thứ hai của bạn bạn không sử dụng các chỉ số để quay trở lại bất cứ điều gì. Bạn chỉ sử dụng chỉ mục để sửa đổi các giá trị. Vì vậy, đối tượng mà bạn đang sửa đổi là đối tượng gốc. Không phải bản sao.

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