2017-12-16 10 views
9

하자 인용 NumPy와 설명서 : https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#advanced-indexing왜 데이터 복사본이 원래 데이터를 수정하는 것처럼 보입니까?

선택 개체, OBJ, (데이터 유형 정수 또는 부울의) 비 튜플 시퀀스 객체, ndarray, 또는 가진 튜플 때

고급 인덱싱가 트리거 이상 하나의 시퀀스 객체 또는 ndarray (정수 또는 부울 데이터 유형). 고급 인덱싱에는 정수와 부울의 두 가지 유형이 있습니다.

고급 인덱싱은 항상 데이터 복사본을 반환합니다 (뷰를 반환하는 기본 슬라이스와 대조).

그런 다음 고급 색인화에서 반환 된 항목을 조작하면 원래 배열이 수정되지 않아야합니다. 그리고 실제로 :

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 

이 인쇄 :

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

그러나 항상 그런 될 것 같지 않습니다. 이상하게도 내가 중간 배열에 [] 연산자에 의해 반환 된 것을 저장하지 않으면 어떻게 든 원래 배열에서 작동합니다.

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 

이 인쇄 : 나는 불평하지 않는

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

이 예제를 고려하시기 바랍니다. 실제로, 이것은 나를 위해 생명의 은인입니다. 그러나, 나는 이것이 왜 효과가 있고, 이것을 정말로 이해하고 싶어하는지 이해하지 못합니다.

최대한 빨리 arr[indexes]을 작성하자마자 배열 복사본을 만듭니다. 후속 *= 5은 원래 배열이 아닌이 복사본에서 작동해야합니다. 그러나이 계산 결과는 변수에 쓰여지지 않으므로 버려야합니다.

분명히 나는 ​​틀 렸습니다.

내 오해는 어디에서 왔습니까?

+3

의 차이는 첫 번째 예는'를 통해가는 것을 __getitem__'가해야하기 때문에 사본을 수행하는 다음, __imul__는 현재 위치에서 곱셈 "마법의 방법"입니다. 두 번째 예제는 'var [idx]'형식의 무언가에 할당하면 구현이보기로 표현할 수없는 무언가에 직접 할당 할 수있게 해주는'var .__ setitem__'을 호출하기 때문에 두 번째 예제는 동일하지 않습니다. –

+1

두 번째 예제는 _assigns_ to'arr [ 색인]'첫 번째 예제는 그렇지 않습니다. –

+3

C++과 달리 'a [b] = c'는 "a [b]'"를 검색하고 "당신이 검색 한 것에 할당"으로 분해 할 수 없습니다. 인덱스 할당은 인덱스 검색 및 일반 할당과는 다른 단일 작업입니다. – user2357112

답변

6

, 그들은 실제로는 근본적으로 다르다. 첫 번째는 'a'라는 이름을 expr에 바인딩합니다. 둘째는 다소 차이가 있습니다 ~ a.__setitem__(x, expr)입니다.실제로 __setitem__이 구현 한 것은 누구나 구현할 수 있지만, 기존의 의미는 x으로 표시된 위치의 a 컨테이너 개체를 expr으로 업데이트하는 것입니다. 특히 "a[x]을 나타내는"중간 개체가 만들어지지 않습니다.

완전성을 위해 a[x]은 l.h.s에 없습니다. 구문 상 어마 어마한 것 같아 보이는 부분은 a.__getitem__(x)과 거의 같습니다. (a[x] *= 5이 실행되면 어떻게됩니까?)

후속 질문에 응답 업데이트 관련 방법이 그래서 우리는 무슨 일이 일어나고 있는지 볼 수있는 악기 우리를 보자.

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 

인쇄 :

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

'a [x] * = 5'를'a [x] = (a [x] * 5)'로 분해 할 수 있습니까? 그러므로'a [x] * = 5'는 'a .__ setitem __ (x, a .__ getitem __ (x) * 5)'와 같은가요? 나는 이것이 옳은가 틀린가? – gaazkam

+0

@gaazkam 거의 오른쪽,'* ='는'l.h.s.'가 내부의 곱셈을 구현하는'__imul__' 메소드를 가지고 있는지 확인합니다. 그래서'a .__ getitem __ (x)'에 의해 리턴 된 중간은'a .__ setitem__'에 전달되기 전에 실제로 변경됩니다. –

+0

@gaazkam 이에 대한 답변을 업데이트했습니다. –

1

핵심은 여기에 항상

고급 색인 반환 데이터 두 번째 예에서

의 사본 당신은 아무것도를 반환하는 인덱스를 사용하지 않을 것입니다. 색인을 사용하여 값을 수정하는 것뿐입니다. 따라서 수정중인 객체가 원래 객체입니다. 사본이 아닙니다. 문

a = expr 

a[x] = expr 

은 비슷하지만