Molti problemi di prestazioni e di leggibilità nel codice non dipendono dall'algoritmo, ma da una scelta più semplice: la struttura dati sbagliata. Una lista usata dove servirebbe un insieme può rendere lentissimo un programma con dati reali; un dizionario usato al posto di una classe può rendere il codice difficile da capire. Conoscere bene le strutture di base di Python, e il costo delle operazioni su ciascuna, è uno degli investimenti più redditizi per chi programma.
Liste: sequenze ordinate e modificabili
La lista è la struttura più usata: una sequenza ordinata di elementi, a cui si accede per posizione.
attivita = ["leggere email", "riunione", "revisione codice"]
attivita.append("aggiornare documentazione")
prima = attivita[0]
Le operazioni hanno costi molto diversi:
- accesso per indice e aggiunta in fondo: veloci, tempo costante (in media per
append); - inserimento o rimozione all'inizio: lenti, O(n), perché tutti gli elementi vanno spostati;
- verifica di appartenenza con
in: O(n), perché la lista va scorsa.
L'ultimo punto è la fonte di molti rallentamenti. Controllare se un elemento è in una lista di centomila voci, all'interno di un ciclo, produce milioni di confronti.
Tuple: sequenze immutabili
Una tupla è simile a una lista, ma non può essere modificata dopo la creazione. Si usa per gruppi di valori che vanno insieme e non devono cambiare: coordinate, una data scomposta, la riga di un risultato.
punto = (12.5, 41.9)
latitudine, longitudine = punto # spacchettamento
L'immutabilità ha un vantaggio pratico: le tuple composte da valori immutabili possono essere usate come chiavi di un dizionario o elementi di un insieme, le liste no. Per tuple con molti campi, namedtuple o le dataclass rendono il codice più leggibile, perché permettono di scrivere punto.lat invece di punto[0].
Dizionari: accesso per chiave
Il dizionario associa chiavi a valori, ed è probabilmente la struttura più potente di Python.
prezzi = {"caffè": 1.20, "cornetto": 1.50}
prezzi["spremuta"] = 3.00
totale = prezzi["caffè"] + prezzi.get("tè", 0)
Lettura, scrittura e verifica di una chiave richiedono in media tempo costante, indipendentemente dalla dimensione del dizionario. Dalla versione 3.7 di Python, inoltre, i dizionari mantengono l'ordine di inserimento come garanzia del linguaggio.
Il metodo get con un valore predefinito evita l'eccezione KeyError quando una chiave può mancare. Le chiavi devono essere oggetti immutabili e confrontabili in modo affidabile: stringhe, numeri, tuple di valori immutabili.
Insiemi: appartenenza e unicità
Un insieme contiene elementi unici, senza un ordine garantito, e risponde in tempo medio costante alla domanda «questo elemento c'è?».
email_note = {"a@esempio.it", "b@esempio.it"}
if nuova_email in email_note:
print("già registrata")
Gli insiemi offrono anche le operazioni della teoria degli insiemi, molto utili per confrontare collezioni:
iscritti_corso_a & iscritti_corso_b # intersezione: iscritti a entrambi
iscritti_corso_a - iscritti_corso_b # differenza: solo al corso A
Rimuovere i duplicati da una lista diventa banale con set(lista), con l'avvertenza che l'ordine originale si perde; se serve mantenerlo, list(dict.fromkeys(lista)) è un'alternativa compatta.
Quale scegliere
| Esigenza | Struttura consigliata |
|---|---|
| Sequenza ordinata da scorrere o modificare | Lista |
| Gruppo fisso di valori correlati | Tupla o dataclass |
| Cercare un valore a partire da una chiave | Dizionario |
| Verificare spesso l'appartenenza o eliminare duplicati | Insieme |
| Aggiungere e togliere da entrambe le estremità | collections.deque |
| Contare occorrenze | collections.Counter |
| Raggruppare valori per chiave | collections.defaultdict(list) |
Il modulo collections
La libreria standard offre strutture specializzate che evitano di reinventare la ruota.
deque è una coda a doppia estremità: aggiungere e rimuovere all'inizio o alla fine costa tempo costante. È la scelta naturale per code di elaborazione o per tenere solo gli ultimi N elementi, grazie al parametro maxlen.
Counter conta le occorrenze in una riga:
from collections import Counter
parole = testo.lower().split()
piu_frequenti = Counter(parole).most_common(5)
defaultdict crea automaticamente un valore iniziale per le chiavi mancanti, eliminando i controlli ripetitivi quando si raggruppano dati:
from collections import defaultdict
ordini_per_cliente = defaultdict(list)
for ordine in ordini:
ordini_per_cliente[ordine.cliente].append(ordine)
Un esempio concreto di scelta
Devi confrontare due elenchi di codici prodotto, uno del magazzino e uno del catalogo online, ciascuno con cinquantamila voci, per trovare i prodotti presenti solo in magazzino. Con due liste e un ciclo con in, il programma esegue un numero di confronti nell'ordine dei miliardi. Convertendo il catalogo in un insieme, ogni verifica diventa immediata e il risultato arriva in una frazione di secondo. Il codice cambia di una riga; le prestazioni cambiano di ordini di grandezza.
In sintesi
- La scelta della struttura dati incide su prestazioni e leggibilità più di quanto sembri.
- Le liste sono ottime per sequenze, ma la verifica di appartenenza costa O(n).
- Dizionari e insiemi offrono accesso e ricerca in tempo medio costante.
- Le tuple rappresentano gruppi fissi di valori e possono essere chiavi di un dizionario.
- Il modulo
collectionsoffredeque,Counteredefaultdictper esigenze comuni.