Quando si lavora con dati numerici in Python, la scelta veramente credibile è l’ndarray di NumPy. Questo oggetto generalizza la semplice lista in un contenitore multidimensionale omogeneo e veloce che sostiene quasi tutto lo stack del calcolo scientifico. In pratica userai soprattutto due ranghi: array monodimensionali (vettori) e bidimensionali (matrici). Ogni rango aggiunge un asse: un vettore possiede un solo asse, una matrice ne possiede due e lo schema si estende manualmente a tensori di ordine superiore.
Ogni array porta con sé una descrizione sintetica:
size restituisce il numero totale di elementi contenuti.shape è una tupla in cui l’elemento i-esimo indica la lunghezza dell’asse i; per una classica matrice $m\times n$ si ha (m, n).ndim fornisce il rango, cioè il numero di assi: una misura scalare della complessità strutturale.dtype dichiara il tipo scalare a livello C (int32, float64, ecc.). La scelta del dtype non è un dettaglio: determina ingombro in memoria, intervallo numerico e prestazioni.La creazione di array è banale, ma va fatta correttamente. Il modo più esplicito è wrappare una sequenza Python:
numpy.array([1, 2, 3], dytpe=numpy.float64)
L’argomento opzionale dtype garantisce storage deterministico e portabile. Copiare un array esistente è altrettanto diretto: y = numpy.array(x) produce una copia che può divergere in sicurezza dall’originale.
Per schemi d’inizializzazione ricorrenti nel codice reale, conviene usare le factory function di NumPy: sono più chiare e veloci dei loop manuali.
| Costruttore | Semantica (esempio) |
|---|---|
numpy.zeros(shape, dtype) |
Array di zeri, ad es. numpy.zeros((2, 3)) |
numpy.ones(shape) |
Array di uni, ad es. numpy.ones(5) |
numpy.arange([start,] stop[, step]) |
Intervallo half-open con passo opzionale, ad es. numpy.arange(0, 6, 2) → [0, 2, 4] |
numpy.eye(5) |
Matrice identità di ordine n |
numpy.linspace(start, stop, num) |
num campioni equispaziati tra gli estremi, inclusi |
Una volta ottenuti i dati, l’aritmetica elemento-per-elemento “funziona e basta”. Gli operatori infissi usuali (+, -, *, /) si applicano a forme compatibili producendo nuovi array:
x + y # somma punto-a-punto
x * y # prodotto punto-a-punto
Se intendi modificare in-place (tipico per array grandi dove le allocazioni pesano), aggiungi il segno di uguale (x += y, x *= y). Questo idioma è sia più veloce sia più frugale in memoria.
Per un autentico prodotto matriciale abbandona *. L’interfaccia canonica è numpy.dot(A, B); nelle versioni moderne di Python puoi preferire l’operatore infisso @ per leggibilità:
C = A @ B # equivalente a numpy.dot(A, B)
Qui va rispettato il contratto algebrico: il numero di colonne di A deve eguagliare il numero di righe di B. NumPy solleverà immediatamente un’eccezione in caso di dimensioni incompatibili: un fallimento precoce che evita ore di corruzione silente.
Gli array non sono bloccati nella loro forma originale. Con reshape puoi reinterpretare lo stesso buffer con una nuova geometria a patto che il numero totale di elementi resti invariato:
x.reshape((2, 3)) # da (3, 2) a (2, 3) senza copia
Poiché non avviene alcun riordinamento, l’operazione costa O(1): concettualmente potente e computazionalmente gratuita.