Додавання рядків документа до названих кортежів?


85

Чи можна легко додати рядок документації до іменованого парню просто?

я намагався

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

але це не ріже. Чи можна це зробити якимось іншим способом?

Відповіді:


53

Ви можете досягти цього, створивши простий, порожній клас обгортки навколо поверненого значення з namedtuple. Зміст створеного мною файлу ( nt.py):

from collections import namedtuple

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

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

Тоді в Python REPL:

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

Або ви можете зробити:

>>> help(nt.Point)  # which outputs...
Довідка щодо класу Point в модулі nt:

клас Point (Point)
 | Точка у 2d-просторі
 |  
 | Порядок дозволу методу:
 | Точка
 | Точка
 | __вбудований __. кортеж
 | __побудований __. об'єкт
 ...

Якщо вам не подобається робити це вручну кожного разу, просто зробити так:

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__

який виводить:

A point in 3d space

2
Чи не буде підкласифікація перетворити namedtupleна повноцінний "об'єкт"? Тим самим втрачаючи частину прибутків від іменних кортежів?
ексгума

5
Якщо ви додасте __slots__ = ()до похідного підкласу, ви зможете зберегти пам'ять та переваги продуктивності використанняnamedtuple
ali_m

Це все ще додає ще один рівень до MRO, що не є виправданим для документації. Однак можна просто призначити __doc__та зберегти в оригінальному об'єкті спеціальну документацію.
Бахсау,

70

У Python 3 обгортка не потрібна, оскільки __doc__атрибути типів можна записувати.

from collections import namedtuple

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

x - the abscissa
y - the ordinate'''

Це тісно відповідає стандартному визначенню класу, де документ-рядок слідує за заголовком.

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

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

У Python 2 це не працює.

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


64

Натрапив на це старе запитання через Google, задаючись питанням про те саме.

Просто хотів зазначити, що ви можете привести його в порядок ще більше, зателефонувавши namedtuple () прямо з оголошення класу:

from collections import namedtuple

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

8
Важливо, щоб ви включили __slots__ = ()в клас. В іншому випадку ви створюєте __dict__для своїх атрибутів, втрачаючи легку природу namedtuple.
BoltzmannBrain

34

Чи можна легко додати рядок документації до іменованого парню просто?

Так, кількома способами.

Набір підкласу. NameedTuple - Python 3.6+

Починаючи з Python 3.6, ми можемо використовувати classвизначення typing.NamedTupleбезпосередньо, із текстом документа (та анотаціями!):

from typing import NamedTuple

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

Порівняно з Python 2, оголошувати порожнім __slots__не потрібно. У Python 3.8 це не потрібно навіть для підкласів.

Зверніть увагу, що оголошення __slots__не може бути порожнім!

У Python 3 ви також можете легко змінити документ у вказаному наборі:

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

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

Що дозволяє нам розглянути наміри щодо них, коли ми закликаємо до них допомогу:

Help on class NT in module __main__:

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

Це дійсно просто порівняно з труднощами, які ми маємо, виконуючи те саме в Python 2.

Python 2

У Python 2 вам це потрібно

  • підклас namedtuple, і
  • заявити __slots__ == ()

Декларування __slots__є важливою частиною, якої інші відповіді тут пропускають .

Якщо ви не заявите __slots__- ви можете додати змінні спеціальні атрибути до екземплярів, вносячи помилки.

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

І зараз:

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

Кожен екземпляр створить окремий __dict__при __dict__доступі (відсутність __slots__не по-іншому перешкоджатиме функціональності, але легкість кортежу, незмінюваність та оголошені атрибути - це всі важливі особливості іменних кортежів).

Вам також знадобиться a __repr__, якщо ви хочете, щоб те, що лунає в командному рядку, дало вам еквівалентний об’єкт:

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__, Як це необхідно , якщо ви створюєте базу namedtuple з іншим ім'ям (як ми робили вище з ім'ям рядка аргументу 'NTBase'):

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

Щоб перевірити повторення, створіть екземпляр, а потім перевіріть рівність переходу до eval(repr(instance))

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

Приклад з документації

У документах також дають такий приклад, про __slots__- я додаю свою власну рядок документацію до нього:

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)

...

Підклас, показаний вище, встановлює __slots__порожній кортеж. Це допомагає знизити вимоги до пам'яті, запобігаючи створенню словників примірників.

Це демонструє використання на місці (як пропонується інша відповідь тут), але зверніть увагу, що використання на місці може стати заплутаним, коли ви переглядаєте порядок роздільної здатності методу, якщо ви налагоджуєте, саме тому я спочатку запропонував використовувати Baseяк суфікс для основи з назвою:

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

Щоб запобігти створенню а __dict__підклассу з класу, який його використовує, ви також повинні оголосити його в підкласі. Дивіться також цю відповідь, щоб отримати додаткові застереження щодо використання__slots__ .


3
Хоча вона не така стисла і чітка, як інші відповіді, це повинна бути прийнята відповідь, оскільки вона підкреслює важливість __slots__. Без цього ви втрачаєте полегшену вагу іменованого кратця.
BoltzmannBrain

7

Починаючи з Python 3.5, текстові рядки для namedtupleоб'єктів можуть бути оновлені.

З нового :

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


3

Не потрібно використовувати клас обгортки, як пропонується прийнятою відповіддю. Просто буквально додайте документ:

from collections import namedtuple

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

Це призводить до: (приклад використання ipython3):

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

In [2]: 

Вуаля!


1
Примітка: Це справедливо тільки для Python 3. В Python 2: AttributeError: attribute '__doc__' of 'type' objects is not writable.
Тейлор Едмістон,

1

Ви можете створити свою власну версію функції фабрики з іменами Tuple від Raymond Hettinger і додати необов’язковий docstringаргумент. Однак було б простіше - і, можливо, краще - просто визначити власну заводську функцію, використовуючи ту саму основну техніку, що і в рецепті. У будь-якому випадку, у підсумку ви отримаєте щось багаторазове.

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

Я створив цю функцію, щоб швидко створити названий кортеж і задокументувати кортеж разом із кожним з його параметрів:

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

Потім ви можете створити новий кортеж:

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

Потім створіть екземпляр описаного названого кортежу з вашими власними даними, тобто.

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

Під час виконання help(MyTuple)через командний рядок python3 показано наступне:

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.

Ви також можете вказати тип параметра за допомогою:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.