O que são nomeadas tuplas?
Uma tupla nomeada é uma tupla.
Faz tudo o que uma tupla pode.
Mas é mais do que apenas uma tupla.
É uma subclasse específica de uma tupla criada programaticamente para sua especificação, com campos nomeados e um comprimento fixo.
Isso, por exemplo, cria uma subclasse de tupla e, além de ter um comprimento fixo (neste caso, três), pode ser usado em qualquer lugar em que uma tupla seja usada sem quebrar. Isso é conhecido como substituibilidade de Liskov.
Novidade no Python 3.6 , podemos usar uma definição de classe comtyping.NamedTuple
para criar um nome nomeado:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
O acima é o mesmo que o abaixo, exceto que o acima tem adicionalmente anotações de tipo e uma sequência de caracteres. A seguir, está disponível no Python 2+:
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)
Isso instancia:
>>> ant = ANamedTuple(1, 'bar', [])
Podemos inspecioná-lo e usar seus atributos:
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']
Explicação mais profunda
Para entender as tuplas nomeadas, primeiro você precisa saber o que é uma tupla. Uma tupla é essencialmente uma lista imutável (não pode ser alterada no local na memória).
Veja como você pode usar uma tupla regular:
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'
Você pode expandir uma tupla com a descompactação iterável:
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
Tuplas nomeadas são tuplas que permitem que seus elementos sejam acessados por nome em vez de apenas índice!
Você faz um nomeado duplo como este:
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])
Você também pode usar uma única sequência com os nomes separados por espaços, um uso um pouco mais legível da API:
>>> Student = namedtuple('Student', 'first last grade')
Como usá-los?
Você pode fazer tudo o que as tuplas podem fazer (veja acima) e fazer o seguinte:
>>> 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')
Um comentarista perguntou:
Em um script ou programa grande, onde geralmente se define uma tupla nomeada?
Os tipos que você cria com namedtuple
são basicamente classes que você pode criar com taquigrafia fácil. Trate-os como aulas. Defina-os no nível do módulo, para que pickle e outros usuários possam encontrá-los.
O exemplo de trabalho, no nível global do módulo:
>>> 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 isso demonstra a falha na pesquisa da definição:
>>> 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 que / quando devo usar tuplas nomeadas em vez de normais?
Use-os quando melhorar o seu código para que a semântica dos elementos da tupla seja expressa no seu código.
Você pode usá-los em vez de um objeto, caso contrário, use um objeto com atributos de dados imutáveis e sem funcionalidade.
Você também pode subclassificá-los para adicionar funcionalidade, por exemplo :
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 que / quando devo usar tuplas normais em vez de tuplas nomeadas?
Provavelmente seria uma regressão deixar de usar tuplas nomeadas para tuplas. A decisão inicial do projeto concentra-se em saber se o custo do código extra envolvido vale a legibilidade aprimorada quando a tupla é usada.
Não há memória extra usada por tuplas nomeadas versus tuplas.
Existe algum tipo de "lista nomeada" (uma versão mutável da tupla nomeada)?
Você está procurando um objeto com fenda que implemente toda a funcionalidade de uma lista com tamanho estatístico ou uma lista subclassificada que funcione como uma tupla nomeada (e que de alguma forma impeça a alteração de tamanho na lista).
Um exemplo agora expandido, e talvez até substituível de Liskov, do primeiro:
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, para usar, basta subclasse e 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