Come si chiamano le tuple?
Una tupla nominata è una tupla.
Fa tutto ciò che una tupla può fare.
Ma è più di una semplice tupla.
È una sottoclasse specifica di una tupla creata a livello di programmazione in base alle specifiche, con campi con nome e lunghezza fissa.
Questo, ad esempio, crea una sottoclasse di tupla e, oltre a essere di lunghezza fissa (in questo caso, tre), può essere usata ovunque una tupla viene utilizzata senza rompersi. Questa è nota come sostituibilità di Liskov.
Nuovo in Python 3.6 , possiamo usare una definizione di classe contyping.NamedTuple
per creare una namedtuple:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
Quanto sopra è lo stesso di quello sotto, tranne per il fatto che sopra ha anche annotazioni di tipo e un dotstring. Di seguito è disponibile in Python 2+:
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)
Questo lo istanzia:
>>> ant = ANamedTuple(1, 'bar', [])
Possiamo ispezionarlo e usare i suoi attributi:
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']
Spiegazione più approfondita
Per capire le tuple nominate, devi prima sapere cos'è una tupla. Una tupla è essenzialmente un elenco immutabile (non può essere modificato sul posto in memoria).
Ecco come potresti usare una tupla normale:
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'
Puoi espandere una tupla con il disimballaggio iterabile:
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
Le tuple nominate sono tuple a cui è possibile accedere ai loro elementi per nome anziché semplicemente per indice!
Fai una coppia con nome come questa:
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])
Puoi anche utilizzare una singola stringa con i nomi separati da spazi, un uso leggermente più leggibile dell'API:
>>> Student = namedtuple('Student', 'first last grade')
Come usarli?
Puoi fare tutto ciò che le tuple possono fare (vedi sopra) e fare quanto segue:
>>> named_student_tuple = Student('Lisa', 'Simpson', 'A')
>>> named_student_tuple.first
'Lisa'
>>> named_student_tuple.last
'Simpson'
>>> named_student_tuple.grade
'A'
>>> named_student_tuple._asdict()
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> vars(named_student_tuple)
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> new_named_student_tuple = named_student_tuple._replace(first='Bart', grade='C')
>>> new_named_student_tuple
Student(first='Bart', last='Simpson', grade='C')
Un commentatore ha chiesto:
In uno script o programma di grandi dimensioni, dove di solito si definisce una tupla con nome?
I tipi con cui crei namedtuple
sono fondamentalmente le classi che puoi creare con una scorciatoia facile. Trattali come se fossero delle lezioni. Definiscili a livello di modulo, in modo che Pickle e altri utenti possano trovarli.
L'esempio funzionante, a livello di modulo globale:
>>> from collections import namedtuple
>>> NT = namedtuple('NT', 'foo bar')
>>> nt = NT('foo', 'bar')
>>> import pickle
>>> pickle.loads(pickle.dumps(nt))
NT(foo='foo', bar='bar')
E questo dimostra l'incapacità di cercare la definizione:
>>> def foo():
... LocalNT = namedtuple('LocalNT', 'foo bar')
... return LocalNT('foo', 'bar')
...
>>> pickle.loads(pickle.dumps(foo()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class '__main__.LocalNT'>: attribute lookup LocalNT on __main__ failed
Perché / quando dovrei usare le tuple nominate anziché le tuple normali?
Usali quando migliora il tuo codice per avere la semantica degli elementi tupla espressa nel tuo codice.
Puoi usarli invece di un oggetto se altrimenti utilizzeresti un oggetto con attributi di dati invariati e nessuna funzionalità.
Puoi anche sottoclassarli per aggiungere funzionalità, ad esempio :
class Point(namedtuple('Point', 'x y')):
"""adding functionality to a named tuple"""
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
Perché / quando dovrei usare le tuple normali invece delle tuple nominate?
Probabilmente sarebbe una regressione passare dall'uso delle tuple nominate alle tuple. La decisione di progettazione iniziale si basa sul fatto che il costo derivante dal codice aggiuntivo in questione valga la leggibilità migliorata quando viene utilizzata la tupla.
Non esiste memoria aggiuntiva utilizzata dalle tuple nominate rispetto alle tuple.
Esiste un qualche tipo di "elenco dei nomi" (una versione mutabile della tupla nominata)?
Stai cercando un oggetto scanalato che implementa tutte le funzionalità di un elenco di dimensioni statiche o un elenco di sottoclassi che funziona come una tupla denominata (e che in qualche modo impedisce all'elenco di cambiare dimensioni).
Un esempio ora ampliato, e forse addirittura sostituibile di Liskov, del primo:
from collections import Sequence
class MutableTuple(Sequence):
"""Abstract Base Class for objects that work like mutable
namedtuples. Subclass and define your named fields with
__slots__ and away you go.
"""
__slots__ = ()
def __init__(self, *args):
for slot, arg in zip(self.__slots__, args):
setattr(self, slot, arg)
def __repr__(self):
return type(self).__name__ + repr(tuple(self))
# more direct __iter__ than Sequence's
def __iter__(self):
for name in self.__slots__:
yield getattr(self, name)
# Sequence requires __getitem__ & __len__:
def __getitem__(self, index):
return getattr(self, self.__slots__[index])
def __len__(self):
return len(self.__slots__)
E per usare, basta sottoclassare e definire __slots__
:
class Student(MutableTuple):
__slots__ = 'first', 'last', 'grade' # customize
>>> student = Student('Lisa', 'Simpson', 'A')
>>> student
Student('Lisa', 'Simpson', 'A')
>>> first, last, grade = student
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
>>> student[0]
'Lisa'
>>> student[2]
'A'
>>> len(student)
3
>>> 'Lisa' in student
True
>>> 'Bart' in student
False
>>> student.first = 'Bart'
>>> for i in student: print(i)
...
Bart
Simpson
A