Aggiunta di docstring a namedtuples?


87

È possibile aggiungere una stringa di documentazione a una namedtuple in modo semplice?

Provai

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)"

ma questo non lo taglia. È possibile fare in qualche altro modo?

Risposte:


53

È possibile ottenere ciò creando una semplice classe wrapper vuota attorno al valore restituito da namedtuple. Contenuto di un file che ho creato ( nt.py):

from collections import namedtuple

Point_ = namedtuple("Point", ["x", "y"])

class Point(Point_):
    """ A point in 2d space """
    pass

Quindi in Python REPL:

>>> print nt.Point.__doc__
 A point in 2d space 

Oppure potresti fare:

>>> help(nt.Point)  # which outputs...
Aiuto sul punto di classe nel modulo nt:

class Point (Punto)
 | Un punto nello spazio 2d
 |  
 | Ordine di risoluzione del metodo:
 | Punto
 | Punto
 | __builtin __. tupla
 | __builtin __. oggetto
 ...

Se non ti piace farlo a mano ogni volta, è banale scrivere una sorta di funzione di fabbrica per farlo:

def NamedTupleWithDocstring(docstring, *ntargs):
    nt = namedtuple(*ntargs)
    class NT(nt):
        __doc__ = docstring
    return NT

Point3D = NamedTupleWithDocstring("A point in 3d space", "Point3d", ["x", "y", "z"])

p3 = Point3D(1,2,3)

print p3.__doc__

quali uscite:

A point in 3d space

2
La sottoclasse non convertirà il namedtuplein un vero e proprio "oggetto"? Perdendo così alcuni dei guadagni in termini di prestazioni dalle tuple con nome?
exhuma

5
Se aggiungi __slots__ = ()alla sottoclasse derivata puoi conservare la memoria e i vantaggi in termini di prestazioni dell'utilizzonamedtuple
ali_m

Aggiunge ancora un altro livello all'MRO, che non è giustificato per una docstring. Tuttavia, si può semplicemente assegnare __doc__e salvare una docstring personalizzata nell'oggetto originale.
Bachsau

71

In Python 3, non è necessario alcun wrapper, poiché gli __doc__attributi dei tipi sono scrivibili.

from collections import namedtuple

Point = namedtuple('Point', 'x y')
Point.__doc__ = '''\
A 2-dimensional coordinate

x - the abscissa
y - the ordinate'''

Ciò corrisponde strettamente a una definizione di classe standard, dove la docstring segue l'intestazione.

class Point():
    '''A 2-dimensional coordinate

    x - the abscissa
    y - the ordinate'''
    <class code>

Questo non funziona in Python 2.

AttributeError: attribute '__doc__' of 'type' objects is not writable.


64

Mi sono imbattuto in questa vecchia domanda tramite Google mentre mi chiedevo la stessa cosa.

Volevo solo far notare che puoi riordinarlo ancora di più chiamando namedtuple () direttamente dalla dichiarazione della classe:

from collections import namedtuple

class Point(namedtuple('Point', 'x y')):
    """Here is the docstring."""

8
Importante da includere __slots__ = ()nella classe. Altrimenti crei un __dict__per i tuoi attrs, perdendo la natura leggera di namedtuple.
BoltzmannBrain

34

È possibile aggiungere una stringa di documentazione a una namedtuple in modo semplice?

Sì, in diversi modi.

Tipo di sottoclasse.NamedTuple - Python 3.6+

A partire da Python 3.6 possiamo usare una classdefinizione typing.NamedTupledirettamente con, con una docstring (e annotazioni!):

from typing import NamedTuple

class Card(NamedTuple):
    """This is a card type."""
    suit: str
    rank: str

Rispetto a Python 2, dichiarando vuoto __slots__ non è necessaria. In Python 3.8, non è necessario nemmeno per le sottoclassi.

Nota che la dichiarazione __slots__ non può essere non vuota!

In Python 3, puoi anche modificare facilmente il documento su una namedtuple:

NT = collections.namedtuple('NT', 'foo bar')

NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""

Ciò ci consente di visualizzare l'intento per loro quando chiamiamo aiuto su di loro:

Help on class NT in module __main__:

class NT(builtins.tuple)
 |  :param str foo: foo name
 |  :param list bar: List of bars to bar
...

Questo è davvero semplice rispetto alle difficoltà che abbiamo nel realizzare la stessa cosa in Python 2.

Python 2

In Python 2, dovrai

  • sottoclasse la namedtuple e
  • dichiarare __slots__ == ()

La dichiarazione __slots__è una parte importante che le altre risposte qui mancano .

Se non dichiari __slots__, potresti aggiungere attributi ad-hoc modificabili alle istanze, introducendo bug.

class Foo(namedtuple('Foo', 'bar')):
    """no __slots__ = ()!!!"""

E adesso:

>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}

Ogni istanza creerà un separato __dict__quando __dict__si accede (la mancanza di __slots__non impedirà altrimenti la funzionalità, ma la leggerezza della tupla, l'immutabilità e gli attributi dichiarati sono tutte caratteristiche importanti di namedtuples).

Avrai anche bisogno di un __repr__, se vuoi che ciò che viene echeggiato sulla riga di comando ti dia un oggetto 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__ = ()

una __repr__come questo è necessario se si crea la namedtuple di base con un altro nome (come abbiamo fatto in precedenza con l'argomento stringa di nome, 'NTBase'):

    def __repr__(self):
        return 'NT(foo={0}, bar={1})'.format(
                repr(self.foo), repr(self.bar))

Per testare la replica, creare un'istanza, quindi verificare l'uguaglianza di un passaggio a eval(repr(instance))

nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt

Esempio dalla documentazione

I documenti forniscono anche un esempio del genere, per quanto riguarda __slots__: sto aggiungendo la mia docstring ad esso:

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 sottoclasse mostrata sopra imposta __slots__una tupla vuota. Ciò aiuta a mantenere bassi i requisiti di memoria impedendo la creazione di dizionari di istanza.

Questo dimostra l'utilizzo sul posto (come suggerisce un'altra risposta qui), ma tieni presente che l'utilizzo sul posto potrebbe creare confusione quando guardi l'ordine di risoluzione del metodo, se stai eseguendo il debug, motivo per cui ho originariamente suggerito di usare Basecome suffisso per la base denominata coppia:

>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
                # ^^^^^---------------------^^^^^-- same names!        

Per impedire la creazione di una __dict__sottoclasse da una classe che la utilizza, è necessario dichiararla anche nella sottoclasse. Vedi anche questa risposta per ulteriori avvertenze sull'uso__slots__ .


3
Sebbene non così concisa e chiara come le altre risposte, questa dovrebbe essere la risposta accettata perché sottolinea l'importanza di __slots__. Senza di esso, stai perdendo il valore leggero di una namedtuple.
BoltzmannBrain

7

A partire da Python 3.5, le docstrings per namedtuple oggetti possono essere aggiornate.

Dal whatsnew :

Point = namedtuple('Point', ['x', 'y'])
Point.__doc__ += ': Cartesian coodinate'
Point.x.__doc__ = 'abscissa'
Point.y.__doc__ = 'ordinate'


3

Non è necessario utilizzare una classe wrapper come suggerito dalla risposta accettata. Aggiungi semplicemente una docstring:

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
Point.__doc__="A point in 2D space"

Ciò si traduce in: (esempio utilizzando ipython3):

In [1]: Point?
Type:       type
String Form:<class '__main__.Point'>
Docstring:  A point in 2D space

In [2]: 

Ecco!


1
Nota: Questo è valido solo per Python 3. In Python 2: AttributeError: attribute '__doc__' of 'type' objects is not writable.
Taylor Edmiston

1

Potresti inventare la tua versione della funzione namedtuple di Raymond Hettinger e aggiungere un docstringargomento opzionale . Tuttavia sarebbe più facile - e probabilmente migliore - definire semplicemente la propria funzione di fabbrica utilizzando la stessa tecnica di base della ricetta. In ogni caso, ti ritroverai con qualcosa di riutilizzabile.

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

0

Ho creato questa funzione per creare rapidamente una tupla con nome e documentare la tupla insieme a ciascuno dei suoi parametri:

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

È quindi possibile creare una nuova tupla denominata:

MyTuple = named_tuple(
    "MyTuple",
    "My named tuple for x,y coordinates",
    x="The x value",
    y="The y value"
)

Quindi istanziare la tupla denominata descritta con i propri dati, ad es.

t = MyTuple(4, 8)
print(t) # prints: MyTuple(x=4, y=8)

Quando si esegue help(MyTuple)tramite la riga di comando python3, viene mostrato quanto segue:

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.

In alternativa, puoi anche specificare il tipo di parametro tramite:

MyTuple = named_tuple(
    "MyTuple",
    "My named tuple for x,y coordinates",
    x=("int", "The x value"),
    y=("int", "The y value")
)

-2

No, puoi solo aggiungere stringhe di documenti a moduli, classi e funzioni (inclusi i metodi)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.