Dodawanie ciągów dokumentów do nazwanych tuk?

85

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

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 wystarcza. Czy można to zrobić w inny sposób?

Rickard
źródło

Odpowiedzi:

53

Możesz to osiągnąć, tworząc prostą, pustą klasę opakowania wokół zwracanej wartości z namedtuple. Zawartość utworzonego przeze mnie pliku ( nt.py):

from collections import namedtuple

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

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

Następnie w Python REPL:

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

Lub możesz zrobić:

>>> help(nt.Point)  # which outputs...
Pomoc dotycząca zajęć Punkt w module nt:

klasa Punkt (Punkt)
 | Punkt w przestrzeni 2-wymiarowej
 |  
 | Kolejność rozdzielczości metod:
 | Punkt
 | Punkt
 | __builtin __. krotka
 | __builtin __. obiekt
 ...

Jeśli nie lubisz robić tego ręcznie za każdym razem, łatwo jest 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ścia:

A point in 3d space
Mark Rushakoff
źródło
2
Czy tworzenie podklas nie przekształci namedtupleklasy w pełnoprawny „obiekt”? W ten sposób tracisz część zysków wydajnościowych z nazwanych krotek?
exhuma
5
Jeśli dodasz __slots__ = ()do pochodnej podklasy, możesz zachować pamięć i zalety wydajności wynikające z używanianamedtuple
ali_m
Nadal dodaje kolejny poziom do MRO, który nie jest uzasadniony dla dokumentacji. Można jednak po prostu przypisać __doc__i zapisać niestandardowy ciąg dokumentów w oryginalnym obiekcie.
Bachsau
70

W Pythonie 3 opakowanie nie jest potrzebne, ponieważ __doc__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, w której ciąg dokumentacyjny występuje po nagłówku.

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.

Terry Jan Reedy
źródło
64

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

Chciałem tylko zwrócić uwagę, ż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."""
CoupleWavyLines
źródło
8
Ważne, aby wziąć udział __slots__ = ()w zajęciach. W przeciwnym razie utworzysz a __dict__dla swoich atrybutów, tracąc lekką naturę namedtuple.
BoltzmannBrain
34

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

Tak, na kilka sposobów.

Wpisywanie podklas NamedTuple - Python 3.6+

Od Pythona 3.6 możemy użyć classdefinicji typing.NamedTuplebezpośrednio, z ciągiem dokumentów (i adnotacjami!):

from typing import NamedTuple

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

W porównaniu z Pythonem 2 deklarowanie pustego elementu __slots__nie jest konieczne. W Pythonie 3.8 nie jest to konieczne nawet w przypadku podklas.

Zwróć uwagę, że deklaracja __slots__nie może być niepusta!

W Pythonie 3 możesz również łatwo zmienić dokument na nazwanym tulei:

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

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

Co pozwala nam zobaczyć ich zamiary, gdy wzywamy ich do pomocy:

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 napotykamy, wykonując to samo w Pythonie 2.

Python 2

W Pythonie 2 musisz

  • podklasę namedtuple i
  • ogłosić __slots__ == ()

Deklarowanie __slots__jest ważną częścią, której brakuje w innych odpowiedziach .

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

I teraz:

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

Każda instancja utworzy oddzielną, __dict__gdy __dict__zostanie uzyskana (brak w __slots__inny sposób nie utrudni funkcjonalności, ale lekkość krotki, niezmienność i zadeklarowane atrybuty są ważnymi cechami nazwanych krotek).

Będziesz także chciał a __repr__, jeśli chcesz, aby to, co jest wyświetlane w linii poleceń, dawał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__ = ()

coś __repr__takiego jest potrzebne, jeśli utworzysz bazę o nazwie tuple z inną nazwą (tak jak zrobiliśmy to powyżej z argumentem nazwa string, 'NTBase'):

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

Aby przetestować repr, utwórz wystąpienie, a następnie przetestuj równość przejścia do eval(repr(instance))

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

Przykład z dokumentacji

Dokumenty podają również taki przykład, dotyczący __slots__- dodaję do niego własny dokument:

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__pustą krotkę. Pomaga to utrzymać niskie wymagania dotyczące pamięci, zapobiegając tworzeniu słowników instancji.

Pokazuje to użycie w miejscu (jak sugeruje inna odpowiedź), ale zwróć uwagę, że użycie w miejscu może być mylące, gdy spojrzysz na kolejność rozwiązywania metod, jeśli debugujesz, dlatego pierwotnie sugerowałem użycie Basejako sufiksu dla podstawy o nazwietuple:

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

Aby zapobiec tworzeniu __dict__klasy podczas tworzenia podklasy z klasy, która jej używa, należy również zadeklarować ją w podklasie. Zobacz także tę odpowiedź, aby uzyskać więcej zastrzeżeń dotyczących używania__slots__ .

Aaron Hall
źródło
3
Chociaż nie jest tak zwięzła i jasna jak inne odpowiedzi, powinna to być akceptowana odpowiedź, ponieważ podkreśla znaczenie __slots__. Bez tego tracisz lekką wartość imiennej sumy.
BoltzmannBrain
7

Od Pythona 3.5, dokumenty dla namedtuple można aktualizować obiektów.

Z whatsnew :

Point = namedtuple('Point', ['x', 'y'])
Point.__doc__ += ': Cartesian coodinate'
Point.x.__doc__ = 'abscissa'
Point.y.__doc__ = 'ordinate'
vishes_shell
źródło
3

Nie ma potrzeby używania klasy opakowującej, jak sugeruje zaakceptowana odpowiedź. Po prostu dosłownie dodaj ciąg dokumentów:

from collections import namedtuple

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

Skutkuje to: (przykład przy użyciu ipython3):

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

In [2]: 

Voilà!

A Sz
źródło
1
Uwaga: To jest ważna tylko dla Pythona 3. W Pythonie 2: AttributeError: attribute '__doc__' of 'type' objects is not writable.
Taylor Edmiston
1

Możesz wymyślić własną wersję funkcji namedtuple factory przez Raymonda Hettingera i dodać opcjonalny docstringargument. Byłoby jednak łatwiej - i prawdopodobnie lepiej - po prostu zdefiniować własną funkcję fabryczną przy użyciu tej samej podstawowej techniki, co w recepturze. Tak czy inaczej, otrzymasz coś 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
martineau
źródło
0

Utworzyłem tę funkcję, aby szybko utworzyć nazwaną krotkę i udokumentować krotkę wraz z każdym z jej parametrów:

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

Następnie możesz utworzyć nową nazwaną krotkę:

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

Następnie utwórz wystąpienie opisanej nazwanej krotki z własnymi danymi, tj.

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

Podczas wykonywania help(MyTuple)za pomocą wiersza poleceń python3 pokazano:

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.

Alternatywnie możesz również określić typ parametru poprzez:

MyTuple = named_tuple(
    "MyTuple",
    "My named tuple for x,y coordinates",
    x=("int", "The x value"),
    y=("int", "The y value")
)
Steven
źródło
-2

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

Jeffrey Aylesworth
źródło