¿Es posible agregar una cadena de documentación a una tupla con nombre de una manera fácil?
Lo intenté
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
"""
A point in 2D space
"""
# Yet another test
"""
A(nother) point in 2D space
"""
Point2 = namedtuple("Point2", ["x", "y"])
print Point.__doc__ # -> "Point(x, y)"
print Point2.__doc__ # -> "Point2(x, y)"
pero eso no es suficiente. ¿Es posible hacerlo de otra manera?
python
docstring
namedtuple
Rickard
fuente
fuente
namedtuple
en un "objeto" completo? ¿Perdiendo así algunas de las ganancias de rendimiento de las tuplas con nombre?__slots__ = ()
a la subclase derivada, puede conservar las ventajas de memoria y rendimiento de usarnamedtuple
__doc__
y guardar una cadena de documentos personalizada en el objeto original.En Python 3, no se necesita ningún contenedor, ya que los
__doc__
atributos de los tipos se pueden escribir.from collections import namedtuple Point = namedtuple('Point', 'x y') Point.__doc__ = '''\ A 2-dimensional coordinate x - the abscissa y - the ordinate'''
Esto se corresponde estrechamente con una definición de clase estándar, donde la cadena de documentos sigue al encabezado.
class Point(): '''A 2-dimensional coordinate x - the abscissa y - the ordinate''' <class code>
Esto no funciona en Python 2.
AttributeError: attribute '__doc__' of 'type' objects is not writable
.fuente
Encontré esta vieja pregunta a través de Google mientras me preguntaba lo mismo.
Solo quería señalar que puede ordenarlo aún más llamando a namedtuple () directamente desde la declaración de la clase:
from collections import namedtuple class Point(namedtuple('Point', 'x y')): """Here is the docstring."""
fuente
__slots__ = ()
en la clase. De lo contrario, crea un__dict__
para sus attrs, perdiendo la naturaleza liviana de namedtuple.Sí, de varias formas.
Escritura de subclase.NamedTuple - Python 3.6+
A partir de Python 3.6 podemos usar una
class
definición contyping.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á
__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: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__
.fuente
__slots__
. Sin él, está perdiendo el valor liviano de una tupla con nombre.Desde Python 3.5, las cadenas de documentación de los
namedtuple
objetos se pueden actualizar.De lo nuevo :
fuente
En Python 3.6+ puede usar:
class Point(NamedTuple): """ A point in 2D space """ x: float y: float
fuente
No es necesario usar una clase contenedora como sugiere la respuesta aceptada. Simplemente agregue literalmente una cadena de documentos:
from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) Point.__doc__="A point in 2D space"
Esto da como resultado: (ejemplo de uso
ipython3
):In [1]: Point? Type: type String Form:<class '__main__.Point'> Docstring: A point in 2D space In [2]:
¡Voilà!
fuente
AttributeError: attribute '__doc__' of 'type' objects is not writable
.Podría inventar su propia versión de la función de fábrica namedtuple de Raymond Hettinger y agregar un
docstring
argumento opcional . Sin embargo, sería más fácil, y posiblemente mejor, simplemente definir su propia función de fábrica utilizando la misma técnica básica que en la receta. De cualquier manera, terminarás con algo reutilizable.from collections import namedtuple def my_namedtuple(typename, field_names, verbose=False, rename=False, docstring=''): '''Returns a new subclass of namedtuple with the supplied docstring appended to the default one. >>> Point = my_namedtuple('Point', 'x, y', docstring='A point in 2D space') >>> print Point.__doc__ Point(x, y): A point in 2D space ''' # create a base class and concatenate its docstring and the one passed _base = namedtuple(typename, field_names, verbose, rename) _docstring = ''.join([_base.__doc__, ': ', docstring]) # fill in template to create a no-op subclass with the combined docstring template = '''class subclass(_base): %(_docstring)r pass\n''' % locals() # execute code string in a temporary namespace namespace = dict(_base=_base, _docstring=_docstring) try: exec template in namespace except SyntaxError, e: raise SyntaxError(e.message + ':\n' + template) return namespace['subclass'] # subclass object created
fuente
Creé esta función para crear rápidamente una tupla con nombre y documentar la tupla junto con cada uno de sus parámetros:
from collections import namedtuple def named_tuple(name, description='', **kwargs): """ A named tuple with docstring documentation of each of its parameters :param str name: The named tuple's name :param str description: The named tuple's description :param kwargs: This named tuple's parameters' data with two different ways to describe said parameters. Format: <pre>{ str: ( # The parameter's name str, # The parameter's type str # The parameter's description ), str: str, # The parameter's name: the parameter's description ... # Any other parameters }</pre> :return: collections.namedtuple """ parameter_names = list(kwargs.keys()) result = namedtuple(name, ' '.join(parameter_names)) # If there are any parameters provided (such that this is not an empty named tuple) if len(parameter_names): # Add line spacing before describing this named tuple's parameters if description is not '': description += "\n" # Go through each parameter provided and add it to the named tuple's docstring description for parameter_name in parameter_names: parameter_data = kwargs[parameter_name] # Determine whether parameter type is included along with the description or # if only a description was provided parameter_type = '' if isinstance(parameter_data, str): parameter_description = parameter_data else: parameter_type, parameter_description = parameter_data description += "\n:param {type}{name}: {description}".format( type=parameter_type + ' ' if parameter_type else '', name=parameter_name, description=parameter_description ) # Change the docstring specific to this parameter getattr(result, parameter_name).__doc__ = parameter_description # Set the docstring description for the resulting named tuple result.__doc__ = description return result
Luego puede crear una nueva tupla con nombre:
MyTuple = named_tuple( "MyTuple", "My named tuple for x,y coordinates", x="The x value", y="The y value" )
Luego, cree una instancia de la tupla nombrada descrita con sus propios datos, es decir.
t = MyTuple(4, 8) print(t) # prints: MyTuple(x=4, y=8)
Al ejecutar a
help(MyTuple)
través de la línea de comando python3, se muestra lo siguiente:Help on class MyTuple: class MyTuple(builtins.tuple) | MyTuple(x, y) | | My named tuple for x,y coordinates | | :param x: The x value | :param y: The y value | | Method resolution order: | MyTuple | builtins.tuple | builtins.object | | Methods defined here: | | __getnewargs__(self) | Return self as a plain tuple. Used by copy and pickle. | | __repr__(self) | Return a nicely formatted representation string | | _asdict(self) | Return a new OrderedDict which maps field names to their values. | | _replace(_self, **kwds) | Return a new MyTuple object replacing specified fields with new values | | ---------------------------------------------------------------------- | Class methods defined here: | | _make(iterable) from builtins.type | Make a new MyTuple object from a sequence or iterable | | ---------------------------------------------------------------------- | Static methods defined here: | | __new__(_cls, x, y) | Create new instance of MyTuple(x, y) | | ---------------------------------------------------------------------- | Data descriptors defined here: | | x | The x value | | y | The y value | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | _fields = ('x', 'y') | | _fields_defaults = {} | | ---------------------------------------------------------------------- | Methods inherited from builtins.tuple: | | __add__(self, value, /) | Return self+value. | | __contains__(self, key, /) | Return key in self. | | __eq__(self, value, /) | Return self==value. | | __ge__(self, value, /) | Return self>=value. | | __getattribute__(self, name, /) | Return getattr(self, name). | | __getitem__(self, key, /) | Return self[key]. | | __gt__(self, value, /) | Return self>value. | | __hash__(self, /) | Return hash(self). | | __iter__(self, /) | Implement iter(self). | | __le__(self, value, /) | Return self<=value. | | __len__(self, /) | Return len(self). | | __lt__(self, value, /) | Return self<value. | | __mul__(self, value, /) | Return self*value. | | __ne__(self, value, /) | Return self!=value. | | __rmul__(self, value, /) | Return value*self. | | count(self, value, /) | Return number of occurrences of value. | | index(self, value, start=0, stop=9223372036854775807, /) | Return first index of value. | | Raises ValueError if the value is not present.
Alternativamente, también puede especificar el tipo de parámetro a través de:
MyTuple = named_tuple( "MyTuple", "My named tuple for x,y coordinates", x=("int", "The x value"), y=("int", "The y value") )
fuente
No, solo puede agregar cadenas de documentos a módulos, clases y funciones (incluidos los métodos)
fuente