6.7. Vistas y copias

Una vista es una segunda ventana sobre el mismo bloque de datos que el origen. No se copia ningún dato; la vista contiene un descriptor nuevo (su propia forma, pasos y dtype) pero comparte el búfer. Las vistas son prácticamente gratuitas.

Una copia solicita a la cámara un búfer nuevo y recorre el origen para rellenarlo. Las copias cuestan tanto tiempo como RAM.

La mayoría de los métodos que cambian la forma producen vistas. La mayoría de los que transforman los datos producen copias. Saber cuál es cuál determina si un bucle intensivo agota la RAM de la cámara.

6.7.1. Reshape

reshape() devuelve un arreglo con la forma solicitada. El número total de elementos debe permanecer sin cambios o se genera un ValueError:

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

El resultado es una vista – m y a comparten datos. Escribir mediante m[0, 0] = 99 cambia también a[0].

Asignar una nueva tupla a shape es una forma abreviada de la misma operación:

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

6.7.2. Transpose

transpose() (o el atajo .T) invierte los ejes. Se implementa invirtiendo los pasos – no se mueve ningún dato:

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

Una vista transpuesta no recorre el bloque de datos de forma contigua. Leer t fila por fila visita las posiciones de memoria 0, 3, 1, 4, 2, 5, no el orden subyacente 0, 1, 2, 3, 4, 5 en el que se disponen los bytes. La aritmética y las reducciones ordinarias lo manejan bien – recorren los pasos – pero tobytes() no puede, porque devuelve el búfer subyacente directamente sin copiar. Los bytes que contiene el búfer no coinciden con el orden que implica la forma de la vista, por lo que el método genera un ValueError en cualquier vista no contigua. Cuando se necesitan los bytes en el orden transpuesto, fuerza primero una copia contigua nueva:

bytes_out = t.copy().tobytes()

6.7.3. Flatten y flat

flatten() devuelve una copia de 1-D del arreglo:

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

Pasa order='C' (por defecto) para recorrer primero el último eje, o order='F' para recorrer primero el primer eje:

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 es la forma de iterador. Produce cada elemento de un ndarray de cualquier rango como escalares, sin asignar una copia plana:

for x in m.flat:
    print(x)

Cuando la aplicación necesita recorrer cada elemento, prefiere flat; cuando necesita un búfer denso de 1-D para pasar a otra función, usa flatten().

6.7.4. Iteración

Iterar un arreglo de 1-D produce escalares; iterar un arreglo de rango superior produce vistas de (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])

Las filas producidas al iterar una matriz son vistas, por lo que modificarlas modifica el origen.

6.7.5. Copias

copy() es la forma explícita de obtener un ndarray independiente cuyas modificaciones no afectan al original. Se asigna un búfer nuevo y se recorre el origen para volcarlo en él:

c = a.copy()

tobytes() devuelve un bytearray que comparte memoria con el bloque de datos del arreglo. Las escrituras a través del bytearray modifican el arreglo in situ. Genera un ValueError si el arreglo no es denso (una vista rebanada, una transposición, …).

tolist() devuelve el contenido como una list de Python posiblemente anidada. Útil para serializar resultados pequeños; costoso para los grandes, porque cada elemento se convierte en un objeto de Python independiente.

6.7.6. Qué operaciones devuelven qué

La regla completa:

Las siguientes operaciones devuelven vistas:

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

  • indexación de un solo eje de un arreglo de rango superior – m[0];

  • iterar un arreglo de n-D;

  • reshape(), cuando la disposición solicitada es compatible;

  • transpose() / .T;

  • frombuffer();

  • asarray(), cuando el dtype coincide.

Las siguientes operaciones devuelven copias:

Recurre a una copia explícita solo cuando realmente se necesite un búfer independiente. En una cámara con RAM limitada, la diferencia entre una vista y una copia suele ser la diferencia entre código que cabe y código que no.