Tuple nominate in Python: dati leggibili con <code>namedtuple</code>

by theArchitect
SHARE
Tuple nominate in Python: dati leggibili con <code>namedtuple</code>
© Guida-HTML5.it

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 prezzo e quantita sono più chiari di nomi generici come x e y.
  • Usa namedtuple per 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.prezzo documenta meglio il codice rispetto a prodotto[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.

Approfondisci con risorse ufficiali

SHARE