É possível adicionar uma string de documentação a um namedtuple de uma maneira fácil?
Sim, de várias maneiras.
Tipagem de subclasse.NamedTuple - Python 3.6+
A partir do Python 3.6, podemos usar uma class
definição com typing.NamedTuple
diretamente, com uma docstring (e anotações!):
from typing import NamedTuple
class Card(NamedTuple):
"""This is a card type."""
suit: str
rank: str
Comparado ao Python 2, declarar vazio __slots__
não é necessário. No Python 3.8, não é necessário nem mesmo para subclasses.
Observe que a declaração __slots__
não pode ser não vazia!
No Python 3, você também pode alterar facilmente o documento em um namedtuple:
NT = collections.namedtuple('NT', 'foo bar')
NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""
O que nos permite ver a intenção deles quando pedimos ajuda para eles:
Help on class NT in module __main__:
class NT(builtins.tuple)
| :param str foo: foo name
| :param list bar: List of bars to bar
...
Isso é muito simples em comparação com as dificuldades que temos para realizar a mesma coisa no Python 2.
Python 2
No Python 2, você precisa
- subclasse o namedtuple, e
- declarar
__slots__ == ()
Declarar __slots__
é uma parte importante que as outras respostas aqui perdem .
Se você não declarar __slots__
- você pode adicionar atributos ad-hoc mutáveis às instâncias, introduzindo bugs.
class Foo(namedtuple('Foo', 'bar')):
"""no __slots__ = ()!!!"""
E agora:
>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}
Cada instância criará um separado __dict__
quando __dict__
for acessada (a falta de__slots__
não impedirá de outra forma a funcionalidade, mas a leveza da tupla, a imutabilidade e os atributos declarados são todos recursos importantes de nomeados).
Você também vai querer um __repr__
, se quiser que o que é ecoado na linha de comando forneça um objeto equivalente:
NTBase = collections.namedtuple('NTBase', 'foo bar')
class NT(NTBase):
"""
Individual foo bar, a namedtuple
:param str foo: foo name
:param list bar: List of bars to bar
"""
__slots__ = ()
um __repr__
como este é necessário se você criar o namedtuple de base com um nome diferente (como fizemos acima com o argumento name string 'NTBase'
):
def __repr__(self):
return 'NT(foo={0}, bar={1})'.format(
repr(self.foo), repr(self.bar))
Para testar o repr, instancie e teste a igualdade de uma passagem para eval(repr(instance))
nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt
Exemplo da documentação
Os documentos também dão um exemplo, com relação a __slots__
- estou adicionando minha própria docstring a ele:
class Point(namedtuple('Point', 'x y')):
"""Docstring added here, not in original"""
__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)
...
A subclasse mostrada acima é definida __slots__
como uma tupla vazia. Isso ajuda a manter os requisitos de memória baixos, evitando a criação de dicionários de instância.
Isso demonstra o uso local (como outra resposta aqui sugere), mas observe que o uso local pode se tornar confuso quando você olha para a ordem de resolução do método, se estiver depurando, que é o motivo pelo qual originalmente sugeri usar Base
como sufixo para a base nomeada, dupla:
>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
# ^^^^^---------------------^^^^^-- same names!
Para evitar a criação de um __dict__
ao criar uma subclasse de uma classe que o utiliza, você também deve declará-lo na subclasse. Veja também esta resposta para mais advertências sobre o uso__slots__
.
namedtuple
em um "objeto" completo? Perdendo assim alguns dos ganhos de desempenho das tuplas nomeadas?