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

결과는 뷰입니다 – ma는 데이터를 공유합니다. 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;

  • frombuffer();

  • asarray(), dtype이 일치할 때.

다음 연산들은 복사본을 반환합니다:

  • copy();

  • flatten();

  • 불리언 인덱싱 – a[mask];

  • 산술 – a + b, a * 2, np.sin(a);

  • array() – 항상 복사하며, 다른 배열로부터도 복사합니다;

  • concatenate().

독립적인 버퍼가 정말로 필요할 때만 명시적인 복사본에 손을 뻗으십시오. RAM이 제한된 카메라에서 뷰와 복사본의 차이는 흔히 들어맞는 코드와 그렇지 않은 코드의 차이가 됩니다.