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