É possível adicionar uma string de documentação a um namedtuple de uma maneira fácil?
eu tentei
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)"
mas isso não resolve. É possível fazer de alguma outra maneira?
python
docstring
namedtuple
Rickard
fonte
fonte
namedtuple
em um "objeto" completo? Perdendo assim alguns dos ganhos de desempenho das tuplas nomeadas?__slots__ = ()
à subclasse derivada, você pode reter as vantagens de memória e desempenho de usarnamedtuple
__doc__
e ter uma docstring personalizada salva no objeto original.No Python 3, nenhum wrapper é necessário, pois os
__doc__
atributos dos tipos podem ser gravados.from collections import namedtuple Point = namedtuple('Point', 'x y') Point.__doc__ = '''\ A 2-dimensional coordinate x - the abscissa y - the ordinate'''
Isso corresponde intimamente a uma definição de classe padrão, onde a docstring segue o cabeçalho.
class Point(): '''A 2-dimensional coordinate x - the abscissa y - the ordinate''' <class code>
Isso não funciona no Python 2.
AttributeError: attribute '__doc__' of 'type' objects is not writable
.fonte
Encontrei esta velha questão através do Google enquanto me perguntava a mesma coisa.
Só queria salientar que você pode organizar ainda mais chamando namedtuple () diretamente da declaração da classe:
from collections import namedtuple class Point(namedtuple('Point', 'x y')): """Here is the docstring."""
fonte
__slots__ = ()
na aula. Caso contrário, você criará um__dict__
para seus atributos, perdendo a natureza leve do namedtuple.Sim, de várias maneiras.
Tipagem de subclasse.NamedTuple - Python 3.6+
A partir do Python 3.6, podemos usar uma
class
definição comtyping.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
__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: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__
.fonte
__slots__
. Sem ele, você está perdendo o valor leve de um namedtuple.Desde Python 3.5, docstrings para
namedtuple
objetos podem ser atualizados.Do whatsnew :
fonte
No Python 3.6+, você pode usar:
class Point(NamedTuple): """ A point in 2D space """ x: float y: float
fonte
Não há necessidade de usar uma classe de wrapper conforme sugerido pela resposta aceita. Simplesmente adicione literalmente uma docstring:
from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) Point.__doc__="A point in 2D space"
Isso resulta em: (exemplo usando
ipython3
):In [1]: Point? Type: type String Form:<class '__main__.Point'> Docstring: A point in 2D space In [2]:
Voilà!
fonte
AttributeError: attribute '__doc__' of 'type' objects is not writable
.Você pode inventar sua própria versão da função de fábrica nomeada de Dupla por Raymond Hettinger e adicionar um
docstring
argumento opcional . No entanto, seria mais fácil - e indiscutivelmente melhor - apenas definir sua própria função de fábrica usando a mesma técnica básica da receita. De qualquer forma, você acabará com algo reutilizável.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
fonte
Eu criei esta função para criar rapidamente uma tupla nomeada e documentar a tupla junto com cada um de seus 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
Você pode então criar uma nova tupla nomeada:
MyTuple = named_tuple( "MyTuple", "My named tuple for x,y coordinates", x="The x value", y="The y value" )
Em seguida, instancie a tupla nomeada descrita com seus próprios dados, ou seja.
t = MyTuple(4, 8) print(t) # prints: MyTuple(x=4, y=8)
Ao executar
help(MyTuple)
por meio da linha de comando python3, o seguinte é mostrado: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.
Como alternativa, você também pode especificar o tipo do parâmetro via:
MyTuple = named_tuple( "MyTuple", "My named tuple for x,y coordinates", x=("int", "The x value"), y=("int", "The y value") )
fonte
Não, você só pode adicionar strings de doc a módulos, classes e funções (incluindo métodos)
fonte