6.2. El ndarray

El ndarray es el tipo que contiene datos numéricos en numpy. Es dos cosas en una: un único bloque empaquetado de datos y un pequeño descriptor delante de ese bloque que indica cómo leerlo.

6.2.1. Dentro de la caja

El bloque de datos contiene todos los elementos del array de extremo a extremo, sin nada adicional entre ellos. Cada elemento ocupa el mismo número de bytes – uno para un array de valores uint8, dos para uint16, cuatro para float. Un array uint8 de 256 elementos son exactamente 256 bytes de datos; los mismos 256 números en una list de Python ocupan un kilobyte – una ranura de 32 bits por elemento sin importar cuántos pocos bits necesite realmente el valor.

El descriptor registra lo que significa el bloque. Cinco valores bastan para describir cualquier array rectangular, sin importar cuántas dimensiones tenga:

  • dtype – el tipo de elemento. Decide cuántos bytes ocupa cada elemento y qué rango de valores puede contener (se trata en Dtypes).

  • itemsize – el ancho en bytes de un elemento, derivado del dtype.

  • ndim – el número de dimensiones (1 para un vector, 2 para una matriz, 3 para un volumen, hasta 4).

  • shape – el tamaño a lo largo de cada dimensión como una tupla.

  • strides – cómo avanzar por el bloque de datos para recorrer cada eje. Se trata en Forma y pasos.

Eso es todo. Cada ruta rápida de numpy – aritmética, reducciones, difusión, rebanado – funciona directamente a partir de esos cinco valores más el puntero a los datos, sin sobrecarga de Python por elemento.

6.2.2. Lo que aporta el diseño

Tres propiedades surgen de «bloque empaquetado + descriptor pequeño» y definen cómo se comporta el resto de la sección.

Las operaciones matemáticas elemento a elemento se ejecutan como una sola llamada. a + b entre dos arrays de forma coincidente suma los dos búferes y escribe un tercero, todo dentro de una sola llamada a la biblioteca. np.sin(a) hace lo mismo para el seno de cada elemento. Los operadores aritméticos, de comparación y a nivel de bits funcionan todos de esta forma.

Ver los mismos datos de otra forma es gratis. Pedir una subregión de un array, o los mismos datos dispuestos bajo una forma distinta, no mueve ningún byte. La operación devuelve un nuevo descriptor que apunta al mismo bloque de datos. El resultado se llama vista – una segunda ventana sobre el mismo búfer subyacente. Escribir a través de una vista escribe en el origen.

Las formas mixtas también funcionan. Un array más corto frente a uno más largo, una fila frente a una matriz, una columna frente a una fila – numpy los alinea mediante difusión, un pequeño conjunto de reglas que deciden qué eje corto se estira para coincidir con el largo. El estiramiento es virtual; no se duplican datos.

6.2.3. Lo que cuesta el diseño

Dos restricciones se derivan del mismo diseño.

Cada elemento tiene el mismo tipo. Una lista puede contener un int junto a un str junto a una lista de otros tres valores int; un ndarray no puede. El dtype se fija en el momento de la construcción. La página Dtypes cubre el pequeño conjunto de tipos que numpy admite y las reglas que surgen de fijar uno.

Hacer crecer un array no es gratis. Una lista mantiene ranuras libres al final y admite .append de forma económica. Un ndarray tiene exactamente el tamaño que necesita; añadir significaría asignar un búfer nuevo y más grande y copiar el contenido antiguo en él. No hay un método append(), a propósito. El patrón correcto en la cámara es preasignar el destino con su tamaño final y rellenarlo; Rendimiento cubre la técnica.

Con un búfer empaquetado y tipado para los datos, un pequeño descriptor para los metadatos y tres garantías de comportamiento (operaciones matemáticas rápidas elemento a elemento, vistas alternativas de los mismos datos sin copia y formas que se difunden), el ndarray es el cimiento sobre el que descansa el resto del capítulo. Cómo cobra existencia realmente un array – a partir de un literal, de una asignación rellenada previamente, de un búfer de un periférico – es la siguiente cuestión práctica.