6.7. Views e cópias

Um view é uma segunda janela sobre o mesmo bloco de dados da origem. Nenhum dado é copiado; o view mantém um descritor novo (com seu próprio formato, strides e dtype) mas compartilha o buffer. Views são essencialmente gratuitos.

Uma cópia pede à câmera um novo buffer e percorre a origem para preenchê-lo. Cópias custam tanto tempo quanto RAM.

A maioria dos métodos que alteram o formato produz views. A maioria dos que transformam dados produz cópias. Saber qual é qual decide se um laço quente esgota a RAM da câmera.

6.7.1. Reshape

reshape() retorna um array com o formato solicitado. O número total de elementos deve permanecer inalterado, ou ValueError é gerado:

a = np.arange(12, dtype=np.uint8)
m = a.reshape((3, 4))

O resultado é um view – m e a compartilham dados. Escrever através de m[0, 0] = 99 altera a[0] também.

Atribuir uma nova tupla a shape é uma forma abreviada da mesma operação:

a = np.arange(9)
a.shape = (3, 3)

6.7.2. Transpose

transpose() (ou o atalho .T) inverte os eixos. Implementado invertendo os strides – nenhum dado é movido:

m = np.arange(6, dtype=np.uint8).reshape((2, 3))
t = m.T                  # shape (3, 2), shares m's buffer

Um view transposto não percorre o bloco de dados de forma contígua. Ler t acima linha por linha visita as posições de memória 0, 3, 1, 4, 2, 5, não a ordem subjacente 0, 1, 2, 3, 4, 5 em que os bytes estão dispostos. A aritmética e as reduções comuns lidam bem com isso – elas avançam pelos strides – mas tobytes() não consegue, porque ela devolve o buffer subjacente diretamente sem copiar. Os bytes que o buffer guarda não correspondem à ordem que o formato do view implica, então o método gera ValueError em qualquer view não contíguo. Quando os bytes são necessários na ordem transposta, force primeiro uma nova cópia contígua:

bytes_out = t.copy().tobytes()

6.7.3. Flatten e flat

flatten() retorna uma cópia 1-D do array:

f = m.flatten()          # new dense 1-D ndarray

Passe order='C' (padrão) para percorrer o último eixo primeiro ou order='F' para o primeiro eixo primeiro:

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 é a forma iterador. Ela produz cada elemento de um ndarray de qualquer posto como escalares, sem alocar uma cópia plana:

for x in m.flat:
    print(x)

Quando a aplicação precisa percorrer cada elemento, prefira flat; quando precisa de um buffer 1-D denso para passar a outra função, use flatten().

6.7.4. Iteração

Iterar um array 1-D produz escalares; iterar um array de posto mais alto produz views (n-1)-D:

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

As linhas produzidas ao iterar uma matriz são views, então modificá-las modifica a origem.

6.7.5. Cópias

copy() é a forma explícita de obter um ndarray independente cujas modificações não afetam o original. Um novo buffer é alocado e a origem é percorrida para preenchê-lo:

c = a.copy()

tobytes() retorna um bytearray que compartilha memória com o bloco de dados do array. Escritas através do bytearray modificam o array in-place. Gera ValueError se o array não for denso (um view fatiado, uma transposição, …).

tolist() retorna o conteúdo como uma list Python possivelmente aninhada. Útil para serializar resultados pequenos; caro para grandes, porque cada elemento se torna um objeto Python separado.

6.7.6. Quais operações retornam o quê

A regra completa:

As operações a seguir retornam views:

  • fatiamento – a[1:5], a[::2], m[:, 0];

  • indexação de um único eixo de um array de posto mais alto – m[0];

  • iterar um array n-D;

  • reshape(), quando o layout solicitado é compatível;

  • transpose() / .T;

  • frombuffer();

  • asarray(), quando o dtype coincide.

As operações a seguir retornam cópias:

Recorra a uma cópia explícita apenas quando um buffer independente for realmente necessário. Em uma câmera com RAM limitada, a diferença entre um view e uma cópia é frequentemente a diferença entre um código que cabe e um que não cabe.