¿Qué se llaman tuplas?
Una tupla nombrada es una tupla.
Hace todo lo que una tupla puede.
Pero es más que una simple tupla.
Es una subclase específica de una tupla que se crea mediante programación según sus especificaciones, con campos con nombre y una longitud fija.
Esto, por ejemplo, crea una subclase de tupla, y además de ser de longitud fija (en este caso, tres), se puede usar en todas partes donde se usa una tupla sin romperse. Esto se conoce como la sustituibilidad de Liskov.
Nuevo en Python 3.6 , podemos usar una definición de clasetyping.NamedTuple
para crear una tupla nombrada:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
Lo anterior es lo mismo que a continuación, excepto que lo anterior también tiene anotaciones de tipo y una cadena de documentos. Lo siguiente está disponible en Python 2+:
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)
Esto lo instancia:
>>> ant = ANamedTuple(1, 'bar', [])
Podemos inspeccionarlo y usar sus atributos:
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']
Explicación más profunda
Para comprender las tuplas con nombre, primero debe saber qué es una tupla. Una tupla es esencialmente una lista inmutable (no se puede cambiar in situ en la memoria).
Así es como puedes usar una tupla regular:
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'
Puede expandir una tupla con desempaquetado iterable:
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
Las tuplas con nombre son tuplas que permiten acceder a sus elementos por nombre en lugar de solo índice.
Haces una tupla nombrada así:
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])
También puede usar una sola cadena con los nombres separados por espacios, un uso ligeramente más legible de la API:
>>> Student = namedtuple('Student', 'first last grade')
¿Cómo usarlos?
Puedes hacer todo lo que las tuplas pueden hacer (ver arriba) así como hacer lo siguiente:
>>> 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 comentarista preguntó:
En un script o programa grande, ¿dónde se suele definir una tupla con nombre?
Los tipos que creas con namedtuple
son básicamente clases que puede crear con una taquigrafía fácil. Trátelos como clases. Defínalos en el nivel del módulo, para que Pickle y otros usuarios puedan encontrarlos.
El ejemplo de trabajo, a nivel de módulo global:
>>> 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')
Y esto demuestra la imposibilidad de buscar la definición:
>>> 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
¿Por qué / cuándo debo usar tuplas con nombre en lugar de tuplas normales?
Úselos cuando mejore su código para tener la semántica de los elementos de tupla expresados en su código.
Puede usarlos en lugar de un objeto si de otro modo usaría un objeto con atributos de datos inmutables y sin funcionalidad.
También puede subclasificarlos para agregar funcionalidad, por ejemplo :
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)
¿Por qué / cuándo debo usar tuplas normales en lugar de tuplas con nombre?
Probablemente sería una regresión cambiar de usar tuplas con nombre a tuplas. La decisión de diseño inicial se centra en si el costo del código adicional involucrado vale la legibilidad mejorada cuando se usa la tupla.
No hay memoria adicional utilizada por tuplas con nombre versus tuplas.
¿Hay algún tipo de "lista con nombre" (una versión mutable de la tupla nombrada)?
Está buscando un objeto ranurado que implemente toda la funcionalidad de una lista de tamaño estático o una lista subclasificada que funciona como una tupla con nombre (y que de alguna manera bloquea el cambio de tamaño de la lista).
Un ejemplo ahora ampliado, y tal vez incluso sustituible de Liskov, del primero:
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__)
Y para usar, solo subclase y defina __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