namedtuple - Sottoclasse di tuple con campi nominativi

Scopo: namedtuple assegna nomi, assieme a indici numerici, a ogni membro.

La tuple standard usa indici numerici per accedere ai propri membri.

# collections_tuple.py

import collections

Person = collections.namedtuple('Persona', 'name age')

bob = ('Bob', 30, 'maschio')
print('Rappresentazione:', bob)

jane = ('Jane', 29, 'femmina')
print('\nCampo per indice:', jane[0])

print('\nCampi per indice:')
for p in [bob, jane]:
    print('{} ha {} anni, {}'.format(*p))

Il che rende le tuple efficaci contenitori per semplici utilizzi.

$ python3 collections_tuple.py

Rappresentazione: ('Bob', 30, 'maschio')

Campo per indice: Jane

Campi per indice:
Bob ha 30 anni, maschio
Jane ha 29 anni, femmina

D'altro canto, ricordare quale indice dovrebbe essere usato per ciascun valore può portare a errori, specialmente se la tuple ha molti campi ed è costruita in una parte del codice molto lontana da quella in cui viene usata. Una namedtuple assegna nomi, assieme a indici numerici, a ogni membro.

Definizione

Le istanze di namedtuple hanno la stessa efficienza per quanto riguarda l'uso della memoria rispetto alle normali tuple visto che non hanno dizionari costruiti per ogni istanza. Ciascun tipo di namedtuple viene rappresentato dalla sua propria classe, creata dalla funzione di factory namedtuple(). Gli argomenti sono il nome della nuova classe e una stringa che contiene i nomi degli elementi.

# collections_namedtuple_person.py

import collections

Person = collections.namedtuple('Persona', 'nome anni')

bob = Person(nome='Bob', anni=30)
print('\nRappresentazione:', bob)

jane = Person(nome='Jane', anni=29)
print('\nCampo per indice:', jane.nome)

print('\nCampi per indice:')
for p in [bob, jane]:
    print('{} ha {} anni'.format(*p))

Come illustrato dall'esempio, è possibile accedere ai campi della namedtuple sia usando la notazione con punto (oggetto.attributo) che utilizzando gli indici posizionali delle tuple standard.

$ python3 collections_namedtuple_person.py

Rappresentazione: Persona(nome='Bob', anni=30)

Campo per indice: Jane

Campi per indice:
Bob ha 30 anni
Jane ha 29 anni

Proprio come una tuple normale, una namedtuple è immutabile. Questa restrizione consente alle istanze di tuple di avere un valore di hash consistente, il che ne rende possibile l'utilizzo come chiavi in dizionari e l'inclusione in insiemi.

# collections_namedtuple_immutable.py

import collections

Person = collections.namedtuple('Persona', 'nome anni')

pat = Person(nome='Pat', anni=12)
print('\nRappresentazione:', pat)

pat.anni = 21

Il tentativo di modificare un valore attraverso l'attributo nominativo solleva una eccezione AttributeError.

$ python3 collections_namedtuple_immutable.py

Rappresentazione: Persona(nome='Pat', anni=12)
Traceback (most recent call last):
  File "collections_namedtuple_immutable.py", line 10, in <module>
    pat.anni = 21
AttributeError: can't set attribute

Nomi di Campo non Validi

I nomi di campo non sono validi se sono ripetuti o sono in conflitto con parole riservate del linguaggio.

# collections_namedtuple_bad_fields.py

import collections

try:
    collections.namedtuple('Persona', 'name class anni')
except ValueError as err:
    print(err)

try:
    collections.namedtuple('Persona', 'nome anni anni')
except ValueError as err:
    print(err)

Mentre i nomi dei campi vengono elaborati, valori non validi causano eccezioni ValueError.

$ python3 collections_namedtuple_bad_fields.py

Type names and field names cannot be a keyword: 'class'
Encountered duplicate field name: 'anni'

In situazioni dove una namedtuple viene creata in base a valori al di fuori del controllo del programma (tipo la rappresentazione delle righe ritornate da una ricerca in un database, dove lo schema non è noto in anticipo), si imposti l'opzione rename a True in modo che i campi non validi vengano rinominati.

# collections_namedtuple_rename.py

import collections

with_class = collections.namedtuple(
    'Persona', 'nome class anni',
    rename=True)
print(with_class._fields)

two_ages = collections.namedtuple(
    'Persona', 'nome anni anni',
    rename=True)
print(two_ages._fields)

I nuovi nomi per i campi rinominati dipendono dal loro indice nella tupla, quindi il campo chiamato class diventa _1 e il campo duplicato anni viene modificato in _2.

$ python3 collections_namedtuple_rename.py

('nome', '_1', 'anni')
('nome', 'anni', '_2')

Attributi Speciali

namedtuple fornisce parecchi metodi e attributi utili per lavorare con sottoclassi e istanze. Tutte queste proprietà built-in hanno nomi prefissati da un carattere di sottolineatura (_) che per convenzione nella maggior parte dei programmi Python indica un attributo privato. Tuttavia per namedtuple il prefisso è concepito per la protezione del nome da collisioni con nomi di attributi forniti dall'utente.

I nomi dei campi passati a namedtuple per definire la nuova classe sono salvati nell'attributo _fields.

# collections_namedtuple_fields.py

import collections

Person = collections.namedtuple('Persona', 'nome anni')

bob = Person(nome='Bob', anni=30)
print('\nRappresentazione:', bob)
print('Campi:', bob._fields)

Sebbene l'argomento sia costituito da una stringa con spazi singoli come separatore, il valore conservato è la sequenza dei singoli nomi.

$ python3 collections_namedtuple_fields.py

Rappresentazione: Persona(nome='Bob', anni=30)
Campi: ('nome', 'anni')

Istanze di namedtuple possono essere convertite in istanze di OrderedDict utilizzando _asdict().

# collections_namedtuple_asdict.py
import collections

Person = collections.namedtuple('Persona', 'nome anni')

bob = Person(nome='Bob', anni=30)
print('\nRappresentazione:', bob)
print('Come Dizionario:', bob._asdict())

Le chiavi di OrderedDict sono nello stesso ordine dei campi della namedtuple.

$ python3 collections_namedtuple_asdict.py

Rappresentazione: Persona(nome='Bob', anni=30)
Come Dizionario: OrderedDict([('nome', 'Bob'), ('anni', 30)])

Il metodo _replace() costruisce una nuova istanza, sostituendo i valori di alcuni campi durante l'operazione.

# collections_namedtuple_replace.py

import collections

Person = collections.namedtuple('Persona', 'nome anni')

bob = Person(nome='Bob', anni=30)
print('\nPrima:', bob)
bob2 = bob._replace(nome='Robert')
print('Dopo:', bob2)
print('Uguali?:', bob is bob2)

Nonostante il nome implichi la modifica dell'oggetto esistente, visto che le istanze di namedtuple sono immutabili, il metodo in realtà ritorna un nuovo oggetto.

$ python3 collections_namedtuple_replace.py

Prima: Persona(nome='Bob', anni=30)
Dopo: Persona(nome='Robert', anni=30)
Uguali?: False

Vedere anche:

namedtuple
La documentazione della libreria standard per questo modulo