Можно ли легко добавить строку документации в именованный набор?
Да, несколькими способами.
Типизация подкласса.NamedTuple - 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 вам потребуется
- подклассифицировать именованный кортеж и
- объявить
__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__
противном случае не будет препятствовать функциональности, но легкость кортежа, неизменяемость и объявленные атрибуты являются важными особенностями namedtuples).
Вам также понадобится 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))
Чтобы проверить repr, создайте экземпляр, а затем проверьте равенство перехода к 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__
.
namedtuple
в полноценный «объект»? Из-за этого теряется прирост производительности от именованных кортежей?