¿Es posible agregar una cadena de documentación a una tupla con nombre de una manera fácil?
Sí, de varias formas.
Escritura de subclase.NamedTuple - Python 3.6+
A partir de Python 3.6 podemos usar una class
definición con typing.NamedTuple
directamente, con una cadena de documentación (¡y anotaciones!):
from typing import NamedTuple
class Card(NamedTuple):
"""This is a card type."""
suit: str
rank: str
En comparación con Python 2, __slots__
no es necesario declarar vacío . En Python 3.8, no es necesario ni siquiera para las subclases.
Tenga en cuenta que la declaración __slots__
no puede ser no vacía.
En Python 3, también puede modificar fácilmente el documento en una tupla con nombre:
NT = collections.namedtuple('NT', 'foo bar')
NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""
Lo que nos permite ver la intención para ellos cuando les pedimos ayuda:
Help on class NT in module __main__:
class NT(builtins.tuple)
| :param str foo: foo name
| :param list bar: List of bars to bar
...
Esto es realmente sencillo en comparación con las dificultades que tenemos para lograr lo mismo en Python 2.
Python 2
En Python 2, necesitará
- subclase la tupla nombrada, y
- declarar
__slots__ == ()
Declarar __slots__
es una parte importante que las otras respuestas aquí pasan por alto .
Si no declara __slots__
, puede agregar atributos ad-hoc mutables a las instancias, lo que introduce errores.
class Foo(namedtuple('Foo', 'bar')):
"""no __slots__ = ()!!!"""
Y ahora:
>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}
Cada instancia creará un __dict__
cuándo __dict__
se accede por separado (la falta de __slots__
no impedirá de otro modo la funcionalidad, pero la ligereza de la tupla, la inmutabilidad y los atributos declarados son características importantes de las tuplas con nombre).
También querrá un __repr__
, si desea que lo que se repite en la línea de comando le dé un 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__ = ()
__repr__
se necesita una como esta si crea la base namedtuple con un nombre diferente (como hicimos anteriormente con el argumento de cadena de nombre 'NTBase'
):
def __repr__(self):
return 'NT(foo={0}, bar={1})'.format(
repr(self.foo), repr(self.bar))
Para probar la repr, instancia, luego prueba la igualdad de un pase a eval(repr(instance))
nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt
Ejemplo de la documentación
Los documentos también dan un ejemplo de este tipo, con respecto a __slots__
: le estoy agregando mi propia cadena de documentos:
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)
...
La subclase que se muestra arriba se establece __slots__
en una tupla vacía. Esto ayuda a mantener bajos los requisitos de memoria al evitar la creación de diccionarios de instancia.
Esto demuestra el uso en el lugar (como sugiere otra respuesta aquí), pero tenga en cuenta que el uso en el lugar puede volverse confuso cuando observa el orden de resolución del método, si está depurando, por lo que originalmente sugerí usarlo Base
como sufijo para la base namedtuple:
>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
# ^^^^^---------------------^^^^^-- same names!
Para evitar la creación de una __dict__
subclase de una clase que la usa, también debe declararla en la subclase. Consulte también esta respuesta para obtener más advertencias sobre el uso__slots__
.
namedtuple
en un "objeto" completo? ¿Perdiendo así algunas de las ganancias de rendimiento de las tuplas con nombre?