Dodawanie docstrings do namedtuples?

Czy można w prosty sposób dodać ciąg dokumentacji do nazwaple?

Próbowałem

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)"
Ale to nie wystarczy. Czy można to zrobić w inny sposób?
Author: martineau, 2009-10-22

9 answers

Możesz to osiągnąć, tworząc prostą, pustą klasę owijającą wokół zwracanej wartości z namedtuple. Zawartość pliku, który utworzyłem (nt.py):

from collections import namedtuple

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

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

Następnie w Pythonie REPL:

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

Lub możesz zrobić:

>>> help(nt.Point)  # which outputs...
Help on class Point in module nt:

class Point(Point)
 |  A point in 2d space
 |  
 |  Method resolution order:
 |      Point
 |      Point
 |      __builtin__.tuple
 |      __builtin__.object
 ...

Jeśli nie lubisz robić tego ręcznie za każdym razem, jest trywialne napisać coś w rodzaju funkcji fabrycznej, aby to zrobić:

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__

Które wyjście:

A point in 3d space
 42
Author: Mark Rushakoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-30 23:24:57

Natknąłem się na to stare pytanie przez Google, zastanawiając się nad tym samym.

Chciałem tylko zaznaczyć, że możesz to jeszcze bardziej uporządkować, wywołując namedtuple () bezpośrednio z deklaracji klasy:

from collections import namedtuple

class Point(namedtuple('Point', 'x y')):
    """Here is the docstring."""
 55
Author: CoupleWavyLines,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-30 23:23:38

W Pythonie 3 nie jest potrzebne żadne opakowanie, ponieważ atrybuty typów są zapisywalne.

from collections import namedtuple

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

x - the abscissa
y - the ordinate'''

To ściśle odpowiada standardowej definicji klasy, gdzie docstring podąża za nagłówkiem.

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

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

To nie działa w Pythonie 2.

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

 51
Author: Terry Jan Reedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-04 23:43:06

Czy można w prosty sposób dodać ciąg dokumentacji do namedtuple?

Python 3

W Pythonie 3 możesz łatwo zmienić dokument na swojej nazwie:

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

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

Który pozwala nam zobaczyć intencje dla nich, gdy wzywamy pomoc na nich:

Help on class NT in module __main__:

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

Jest to naprawdę proste w porównaniu z trudnościami, jakie mamy, aby osiągnąć to samo w Pythonie 2.

Python 2

W Pythonie 2, będziesz potrzebował do

  • podklasa nazwa_tuple i
  • declare __slots__ == ()

Deklarowanie __slots__ jest ważną częścią, której brakuje innym odpowiedziom .

Jeśli nie zadeklarujesz __slots__ - możesz dodać zmienne atrybuty ad-hoc do instancji, wprowadzając błędy.

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

A teraz:

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

Każda instancja utworzy oddzielny __dict__, gdy dostęp do __dict__ jest dostępny (brak __slots__ w inny sposób nie utrudni funkcjonalności, ale lekkość krotki, niezmienność i deklarowane atrybuty są ważnymi cechami nazw krotek).

Będziesz również chciał __repr__, jeśli chcesz, aby to, co jest wyświetlane w wierszu poleceń, dało ci równoważny obiekt:

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__ = ()

A __repr__ Jak to jest potrzebne, jeśli utworzysz bazę nazwytuple z inną nazwą (tak jak zrobiliśmy powyżej z argumentem name string, 'NTBase'):

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

Aby przetestować repr, instantiate, następnie test na równość przejścia do eval(repr(instance))

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

Przykład z dokumentacji

Docs również daje taki przykład, dotyczący __slots__ - dodaję do niego swój własny docstring:

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)

...

Podklasa pokazana powyżej ustawia __slots__ na pustą krotkę. To pomaga utrzymuj niskie wymagania dotyczące pamięci, zapobiegając tworzeniu instancji słowniki.

To pokazuje użycie in-place( jak sugeruje inna odpowiedź tutaj), ale zauważ, że in-place użycie może stać się mylące, gdy patrzysz na kolejność rozdzielczości metody, jeśli debugujesz, dlatego pierwotnie zasugerowałem użycie Base jako sufiksu dla bazy nazwastuple:

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

Aby zapobiec tworzeniu __dict__ podczas podklasowania z klasy, która go używa, musisz również zadeklarować ją w podklasie. Zobacz również ta odpowiedź, aby uzyskać więcej zastrzeżeń dotyczących używania __slots__.

 22
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-28 02:46:09

Od wersji Pythona 3.5 docstringi dla obiektów namedtuple mogą być aktualizowane.

From the whatsnew :

Point = namedtuple('Point', ['x', 'y'])
Point.__doc__ += ': Cartesian coodinate'
Point.x.__doc__ = 'abscissa'
Point.y.__doc__ = 'ordinate'
 5
Author: vishes_shell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-26 18:49:56

Nie ma potrzeby używania klasy wrapper, zgodnie z sugestią zaakceptowanej odpowiedzi. Po prostu dosłownie dodaj a docstring:

from collections import namedtuple

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

Daje to: (przykład użycia ipython3):

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

In [2]: 

voila!

 3
Author: A Sz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-24 12:48:47

Możesz wymyślić własną wersję namedtuple Factory function by Raymond Hettinger and add an optional docstring argument.  Jednak byłoby łatwiej-i prawdopodobnie lepiej-po prostu zdefiniować własną funkcję fabryczną przy użyciu tej samej podstawowej techniki, jak w przepisie.  Tak czy inaczej, skończysz z czymś wielokrotnego użytku.

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
 1
Author: martineau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-30 23:26:38

W Pythonie 3.6+ możesz użyć:

class Point(NamedTuple):
    """
    A point in 2D space
    """
    x: float
    y: float
 0
Author: Docent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-07 13:40:12

Nie, można dodawać tylko ciągi dokumentów do modułów, klas i funkcji (w tym metod)

 -2
Author: Jeffrey Aylesworth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-22 10:58:34