Introduzione
Una tupla è una struttura dati ordinata e immutabile: dopo la sua creazione, gli elementi non possono essere modificati. Questa caratteristica è utile quando vogliamo rappresentare un insieme di valori che deve rimanere stabile, ad esempio le coordinate di un punto, una misura con unità di riferimento oppure i dati essenziali di un prodotto.
Un limite delle tuple tradizionali è la leggibilità. Consideriamo questa struttura:
prodotto = ("Tastiera meccanica", 79.90, 12) Per capire il significato dei valori dobbiamo ricordare che il primo elemento è il nome, il secondo il prezzo e il terzo la quantità. Scrivere prodotto[1] non comunica immediatamente l’intenzione del programma.
La soluzione pratica è utilizzare una tupla nominata, cioè un oggetto che conserva i vantaggi principali delle tuple, ma permette di accedere ai campi tramite nomi descrittivi. In Python possiamo crearla con collections.namedtuple. Questo strumento è particolarmente utile quando gestiamo record semplici e immutabili, senza aver bisogno della complessità di una classe completa.
Nel tutorial costruiremo un piccolo catalogo di prodotti usando tuple nominate. Calcoleremo il valore del magazzino, filtreremo i prodotti disponibili e convertiremo i record in dizionari quando necessario.
Codice completo
from collections import namedtuple
# Definizione di un nuovo tipo di tupla nominata.
# I campi descrivono le proprietà fondamentali di un prodotto.
Prodotto = namedtuple("Prodotto", ["nome", "prezzo", "quantita"])
# Creazione di alcuni record immutabili.
catalogo = [
Prodotto("Tastiera meccanica", 79.90, 12),
Prodotto("Mouse ergonomico", 34.50, 0),
Prodotto("Webcam HD", 49.99, 7),
]
# Accesso ai dati tramite nomi leggibili.
for prodotto in catalogo:
valore = prodotto.prezzo * prodotto.quantita
print(
f"{prodotto.nome}: "
f"{prodotto.quantita} pezzi, "
f"valore magazzino €{valore:.2f}"
)
# Selezione dei prodotti attualmente disponibili.
disponibili = [
prodotto for prodotto in catalogo
if prodotto.quantita > 0
]
print("nProdotti disponibili:")
for prodotto in disponibili:
print(f"- {prodotto.nome}")
# Una namedtuple può essere convertita in un dizionario.
primo_prodotto = catalogo[0]
dati_prodotto = primo_prodotto._asdict()
print("nFormato dizionario:")
print(dati_prodotto)
# Per ottenere una nuova tupla con un valore diverso
# si usa _replace(), senza modificare l´oggetto originale.
prodotto_aggiornato = primo_prodotto._replace(prezzo=74.90)
print("nPrezzo precedente:", primo_prodotto.prezzo)
print("Prezzo aggiornato:", prodotto_aggiornato.prezzo)
# La tupla originale è rimasta invariata.
print("Record originale:", primo_prodotto) Spiegazione
Definire una tupla nominata
La funzione namedtuple() riceve due informazioni principali: il nome del nuovo tipo e l’elenco dei campi. Nell’esempio:
Prodotto = namedtuple("Prodotto", ["nome", "prezzo", "quantita"]) Prodotto diventa un nuovo tipo che si comporta come una tupla, ma possiede attributi chiamati nome, prezzo e quantita. Possiamo quindi creare un record con:
articolo = Prodotto("Monitor", 199.99, 4) L’accesso tramite attributi rende il codice più comprensibile:
print(articolo.nome)
print(articolo.prezzo) È comunque possibile usare gli indici tradizionali, perché una tupla nominata rimane compatibile con le operazioni fondamentali delle tuple:
print(articolo[0]) # Monitor
print(len(articolo)) # 3 Immutabilità e aggiornamento dei record
Una tupla nominata non può essere modificata direttamente. Questa istruzione genera un errore:
articolo.prezzo = 189.99 L’immutabilità protegge i dati da modifiche accidentali durante l’esecuzione del programma. Quando serve una variante del record, si usa il metodo _replace(). Questo metodo restituisce una nuova istanza e lascia intatta quella originale:
scontato = articolo._replace(prezzo=189.99) Questo approccio è utile nei programmi che devono mantenere una cronologia dei valori o che preferiscono lavorare con dati prevedibili e non modificabili.
Conversione in dizionario
Il metodo _asdict() converte la tupla nominata in un dizionario. Il risultato è comodo quando dobbiamo serializzare i dati, passarli a una libreria oppure trasformarli in JSON:
record = articolo._asdict()
print(record["nome"]) La conversione è esplicita: il record originale continua a essere una tupla nominata e mantiene la propria immutabilità.
Quando scegliere questa soluzione
Le tuple nominate sono adatte a record semplici, con un numero fisso di campi e comportamenti limitati. Sono una buona scelta per coordinate, righe lette da un file CSV, risultati di query o configurazioni leggere. Se invece il modello deve contenere validazione, metodi complessi, ereditarietà o valori predefiniti articolati, è preferibile usare una classe o una dataclass.
Best practice
- Scegli nomi descrittivi: campi come
prezzoequantitasono più chiari di nomi generici comexey. - Usa
namedtupleper record semplici: non trasformarlo in una classe complessa piena di logica. - Non modificare direttamente i campi: usa
_replace()per creare una nuova versione del record. - Preferisci gli attributi agli indici:
prodotto.prezzodocumenta meglio il codice rispetto aprodotto[1]. - Valuta alternative moderne: per nuovi progetti, una
dataclass(frozen=True)può offrire tipizzazione, valori predefiniti e maggiore estensibilità. - Evita campi mutabili senza necessità: anche se la tupla è immutabile, un campo contenente una lista può ancora essere modificato internamente.
- Mantieni stabile il significato dei campi: cambiare il loro ordine può rendere il codice esistente difficile da comprendere o incompatibile.
Riepilogo
Le tuple nominate combinano l’ordine e l’immutabilità delle tuple con la leggibilità degli attributi. Grazie a namedtuple, un record come ("Monitor", 199.99, 4) diventa un oggetto espressivo, accessibile tramite nome, prezzo e quantita.
Abbiamo visto come definire un tipo, creare più record, filtrare una collezione, calcolare valori, convertire i dati in dizionari e creare copie aggiornate con _replace(). Questa tecnica è indicata quando i dati hanno una struttura fissa e non richiedono comportamenti complessi. Per modelli più evoluti, possiamo passare alle dataclass o alle classi tradizionali.
