6.2. L’ndarray¶
L”ndarray è il tipo che contiene dati numerici in numpy. È due cose in una: un singolo blocco compatto di dati e un piccolo descrittore davanti a quel blocco che indica come leggerlo.
6.2.1. Dentro la scatola¶
Il blocco di dati contiene ogni elemento dell’array uno dietro l’altro, senza nulla di superfluo tra di essi. Ogni elemento occupa lo stesso numero di byte – uno per un array di valori uint8, due per uint16, quattro per float. Un array uint8 di 256 elementi è esattamente 256 byte di dati; gli stessi 256 numeri in una list Python occupano un kilobyte – uno slot a 32 bit per elemento indipendentemente da quanti pochi bit il valore richieda effettivamente.
Il descrittore registra cosa significa il blocco. Cinque valori sono sufficienti a descrivere qualsiasi array rettangolare, indipendentemente dal numero di dimensioni:
dtype– il tipo degli elementi. Decide quanti byte occupa ogni elemento e quale intervallo di valori può contenere (trattato in Dtype).itemsize– la larghezza in byte di un elemento, derivata dal dtype.ndim– il numero di dimensioni (1 per un vettore, 2 per una matrice, 3 per un volume, fino a 4).shape– la dimensione lungo ciascuna dimensione, come tupla.strides– come avanzare attraverso il blocco di dati per percorrere ciascun asse. Trattato in Forma e strides.
Tutto qui. Ogni percorso veloce in numpy – aritmetica, riduzioni, broadcasting, slicing – lavora direttamente a partire da quei cinque valori più il puntatore ai dati, senza alcun sovraccarico Python per elemento.
6.2.2. Cosa offre questo design¶
Tre proprietà derivano da «blocco compatto + piccolo descrittore» e definiscono come si comporta il resto della sezione.
La matematica elemento per elemento avviene in un’unica chiamata. a + b tra due array di forma corrispondente somma i due buffer e ne scrive un terzo, il tutto all’interno di un’unica chiamata di libreria. np.sin(a) fa lo stesso per il seno di ogni elemento. Gli operatori aritmetici, di confronto e bit a bit funzionano tutti in questo modo.
Guardare gli stessi dati in un modo diverso è gratuito. Richiedere una sotto-regione di un array, o gli stessi dati disposti secondo una forma diversa, non sposta alcun byte. L’operazione restituisce un nuovo descrittore che punta allo stesso blocco di dati. Il risultato si chiama vista – una seconda finestra sullo stesso buffer sottostante. Scrivere attraverso una vista scrive sulla sorgente.
Forme miste funzionano comunque. Un array più corto contro uno più lungo, una riga contro una matrice, una colonna contro una riga – numpy li allinea tramite broadcasting, un piccolo insieme di regole che decidono quale asse corto si estende per coincidere con quello lungo. L’estensione è virtuale; nessun dato viene duplicato.
6.2.3. Cosa costa questo design¶
Dallo stesso design derivano due restrizioni.
Ogni elemento ha lo stesso tipo. Una lista può contenere un int accanto a una str accanto a una lista di altri tre valori int; un ndarray no. Il dtype è fissato al momento della costruzione. La pagina Dtype tratta il piccolo insieme di tipi che numpy supporta e le regole che derivano dal fissarne uno.
Far crescere un array non è gratuito. Una lista mantiene slot di riserva alla fine e supporta .append a basso costo. Un ndarray ha esattamente la dimensione di cui ha bisogno; aggiungere elementi significherebbe allocare un nuovo buffer più grande e copiarvi il vecchio contenuto. Non esiste un metodo append(), di proposito. Il pattern corretto sulla camera è pre-allocare la destinazione alla sua dimensione finale e riempirla; Prestazioni tratta la tecnica.
Con un buffer tipizzato e compatto per i dati, un piccolo descrittore per i metadati e tre garanzie comportamentali (matematica elemento per elemento veloce, viste alternative degli stessi dati senza copia e forme che si propagano tramite broadcasting), l”ndarray è la fondamenta su cui poggia il resto del capitolo. Come un array prenda effettivamente vita – da un letterale, da un’allocazione pre-riempita, da un buffer di una periferica – è la prossima questione pratica.