6.7. 뷰와 복사본¶
뷰는 원본과 동일한 데이터 블록을 들여다보는 두 번째 창입니다. 데이터는 복사되지 않으며, 뷰는 새로운 디스크립터(자체 shape, strides, dtype)를 가지지만 버퍼는 공유합니다. 뷰는 사실상 비용이 없습니다.
복사본은 카메라에 새 버퍼를 요청하고 원본을 순회하며 채워 넣습니다. 복사본은 시간과 RAM 모두를 소모합니다.
형태를 바꾸는 메서드 대부분은 뷰를 생성합니다. 데이터를 변환하는 메서드 대부분은 복사본을 생성합니다. 어느 것이 어느 것인지 아는 것이 핫 루프가 카메라의 RAM을 고갈시킬지 여부를 결정합니다.
6.7.1. Reshape¶
reshape()는 요청된 형태의 배열을 반환합니다. 전체 요소 개수가 변하지 않아야 하며 그렇지 않으면 ValueError가 발생합니다:
a = np.arange(12, dtype=np.uint8)
m = a.reshape((3, 4))
결과는 뷰입니다 – m과 a는 데이터를 공유합니다. m[0, 0] = 99를 통해 기록하면 a[0]도 변경됩니다.
shape에 새 튜플을 할당하는 것은 같은 연산의 단축 표현입니다:
a = np.arange(9)
a.shape = (3, 3)
6.7.2. Transpose¶
transpose()(또는 .T 단축형)는 축을 뒤집습니다. 스트라이드를 뒤집는 방식으로 구현되어 – 데이터는 이동하지 않습니다:
m = np.arange(6, dtype=np.uint8).reshape((2, 3))
t = m.T # shape (3, 2), shares m's buffer
전치된 뷰는 데이터 블록을 연속적으로 순회하지 않습니다. 위의 t를 행 단위로 읽으면 바이트가 배치된 기저 순서인 0, 1, 2, 3, 4, 5가 아니라 0, 3, 1, 4, 2, 5 메모리 위치를 방문합니다. 평범한 산술과 리덕션은 이를 문제없이 처리합니다 – 스트라이드를 따라 진행하기 때문입니다 – 그러나 tobytes()는 복사 없이 기저 버퍼를 직접 돌려주기 때문에 그렇게 할 수 없습니다. 버퍼가 보관하는 바이트가 뷰의 형태가 의미하는 순서와 일치하지 않으므로, 이 메서드는 연속적이지 않은 모든 뷰에 대해 ValueError를 발생시킵니다. 전치된 순서로 바이트가 필요할 때는 먼저 새로운 연속적 복사본을 강제로 만드십시오:
bytes_out = t.copy().tobytes()
6.7.3. Flatten과 flat¶
flatten()은 배열의 1차원 복사본을 반환합니다:
f = m.flatten() # new dense 1-D ndarray
order='C'(기본값)를 전달하면 마지막 축을 먼저 순회하고 order='F'를 전달하면 첫 번째 축을 먼저 순회합니다:
m = np.arange(6, dtype=np.uint8).reshape((2, 3))
# m = [[0, 1, 2],
# [3, 4, 5]]
m.flatten() # array([0, 1, 2, 3, 4, 5], dtype=uint8)
m.flatten(order='F') # array([0, 3, 1, 4, 2, 5], dtype=uint8)
flat은 이터레이터 형태입니다. 평탄한 복사본을 할당하지 않고 임의 차원의 ndarray의 모든 요소를 스칼라로 산출합니다:
for x in m.flat:
print(x)
애플리케이션이 모든 요소를 순회해야 할 때는 flat을 선호하십시오. 다른 함수에 넘길 조밀한 1차원 버퍼가 필요할 때는 flatten()을 사용하십시오.
6.7.4. 반복(Iteration)¶
1차원 배열을 반복하면 스칼라가 산출되고, 고차원 배열을 반복하면 (n-1)차원 뷰가 산출됩니다:
m = np.array([[0, 1, 2], [3, 4, 5]], dtype=np.uint8)
for row in m:
print(row) # array([0, 1, 2]), array([3, 4, 5])
행렬을 반복하여 산출되는 행들은 뷰이므로, 이를 수정하면 원본이 수정됩니다.
6.7.5. 복사본¶
copy()는 수정이 원본에 영향을 주지 않는 독립적인 ndarray를 얻는 명시적인 방법입니다. 새 버퍼가 할당되고 원본이 그 안으로 순회됩니다:
c = a.copy()
tobytes()는 배열의 데이터 블록과 메모리를 공유하는 bytearray를 반환합니다. 이 bytearray를 통한 기록은 배열을 제자리에서 수정합니다. 배열이 조밀하지 않으면(슬라이스된 뷰, 전치 등) ValueError를 발생시킵니다.
tolist()는 내용을 중첩될 수 있는 Python list로 반환합니다. 작은 결과를 직렬화하는 데 유용하지만, 큰 결과에는 비용이 큽니다. 모든 요소가 개별 Python 객체가 되기 때문입니다.
6.7.6. 어떤 연산이 무엇을 반환하는가¶
전체 규칙:
다음 연산들은 뷰를 반환합니다:
슬라이싱 –
a[1:5],a[::2],m[:, 0];고차원 배열의 단일 축 인덱싱 –
m[0];n차원 배열의 반복;
reshape(), 요청된 레이아웃이 호환될 때;transpose()/.T;asarray(), dtype이 일치할 때.
다음 연산들은 복사본을 반환합니다:
불리언 인덱싱 –
a[mask];산술 –
a + b,a * 2,np.sin(a);array()– 항상 복사하며, 다른 배열로부터도 복사합니다;
독립적인 버퍼가 정말로 필요할 때만 명시적인 복사본에 손을 뻗으십시오. RAM이 제한된 카메라에서 뷰와 복사본의 차이는 흔히 들어맞는 코드와 그렇지 않은 코드의 차이가 됩니다.