Как документировать атрибуты класса в Python?

Я пишу легкий класс, чьи атрибуты предназначены для публичного доступа и только иногда переопределяются в конкретных экземплярах. На языке Python нет никаких условий для создания docstrings для атрибутов класса или любых атрибутов. Каков приемлемый способ, должен ли он быть, документировать эти атрибуты? В настоящее время я делаю такие вещи:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
    """

    flight_speed = 691
    __doc__ += """
        flight_speed (691)
          The maximum speed that such a bird can attain.
    """

    nesting_grounds = "Raymond Luxury-Yacht"
    __doc__ += """
        nesting_grounds ("Raymond Luxury-Yacht")
          The locale where these birds congregate to reproduce.
    """

    def __init__(self, **keyargs):
        """Initialize the Albatross from the keyword arguments."""
        self.__dict__.update(keyargs)

Это приведет к тому, что класс docstring, содержащий начальную стандартную строку docstring, а также строки, добавленные для каждого атрибута, с помощью расширенного присваивания __doc__.

Хотя этот стиль явно не запрещен в правилах стиля docstring, он также не упоминается как опция. Преимущество здесь в том, что он обеспечивает способ документировать атрибуты наряду с их определениями, при этом создавая презентабельную docstring класса и избегая необходимости писать комментарии, которые повторяют информацию из docstring. Меня все еще раздражает, что я должен на самом деле написать атрибуты дважды; Я рассматриваю возможность использования строковых представлений значений в docstring, чтобы избежать дублирования значений по умолчанию.

Является ли это отвратительным нарушением специальных конвенций сообщества? Это нормально? Есть ли способ лучше? Например, можно создать словарь, содержащий значения и docstrings для атрибутов, а затем добавить содержимое в класс __dict__ и docstring в конец объявления класса; это облегчит необходимость ввода имен атрибутов и значений дважды. edit: эта последняя идея, по-моему, на самом деле не возможна, по крайней мере, не без динамического построения всего класса из данных, что кажется действительно плохим идеей, если нет других причин для этого.

Я новичок в python и все еще разрабатываю детали стиля кодирования, поэтому также приветствуются не связанные с критикой критические замечания.

Ответ 1

Чтобы избежать путаницы: термин свойство имеет особое значение в Python. То, о чем вы говорите, это то, что мы называем атрибутами класса. Поскольку к ним всегда обращаются через их класс, я считаю, что имеет смысл документировать их в строке документа класса. Что-то вроде этого:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
        flight_speed     The maximum speed that such a bird can attain.
        nesting_grounds  The locale where these birds congregate to reproduce.
    """
    flight_speed = 691
    nesting_grounds = "Throatwarbler Man Grove"

Я думаю, что это намного проще для глаз, чем подход в вашем примере. Если бы я действительно хотел, чтобы копия значений атрибута появлялась в строке документа, я бы поместил их рядом с описанием каждого атрибута или под ним.

Помните, что в Python строки документов являются фактическими членами объектов, которые они документируют, а не просто аннотациями исходного кода. Поскольку переменные атрибутов класса не являются самими объектами, а являются ссылками на объекты, они не могут содержать собственные строки документов. Я полагаю, вы могли бы привести аргументы в пользу строк документации на ссылках, возможно, описать "что должно быть здесь" вместо "что на самом деле здесь", но я считаю, что это довольно легко сделать в содержащей строке класса документа.

Ответ 2

Вы цитируете PEP257: Условные обозначения, в разделе Что такое указанная строка

Строковые литералы, встречающиеся в других местах кода Python, также могут выступать в качестве документации. Они не распознаются компилятором байт-кода Python и недоступны в качестве атрибутов объекта среды выполнения (т.е. не назначаются для __doc__), но программные инструменты могут извлекать два типа дополнительных строк документации:

Строковые литералы, встречающиеся сразу после простого присваивания на верхнем уровне модуля, класса или метода __init__, называются "строки документации атрибута".

И это более подробно объясняется в PEP 258: Строки документации атрибутов. Как объясняется выше ʇsәɹoɈ. атрибут не является объектом, которому может принадлежать __doc__, поэтому он не будет отображаться в help() или pydoc. Эти строки документации могут использоваться только для сгенерированной документации.

Они используются в Sphinx с директивой autoattribute

Sphinx может использовать комментарии в строке перед назначением или специальный комментарий после назначения или строку документации после определения, которое будет автоматически документировано.

Ответ 3

Вы можете злоупотреблять свойствами с этой целью. Свойства содержат получатель, установщик, удалитель и строку документации. Наивно это получилось бы очень многословно:

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """Docstring goes here."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Тогда у вас будет строка документации, принадлежащая Cx:

In [24]: print(C.x.__doc__)
Docstring goes here.

Делать это для многих атрибутов довольно сложно, но вы можете представить вспомогательную функцию myprop:

def myprop(x, doc):
    def getx(self):
        return getattr(self, '_' + x)

    def setx(self, val):
        setattr(self, '_' + x, val)

    def delx(self):
        delattr(self, '_' + x)

    return property(getx, setx, delx, doc)

class C:
    a = myprop("a", "Hi, I'm A!")
    b = myprop("b", "Hi, I'm B!")

In [44]: c = C()

In [46]: c.b = 42

In [47]: c.b
Out[47]: 42

In [49]: print(C.b.__doc__)
Hi, I'm B!

Затем, вызов интерактивной help Pythons даст:

Help on class C in module __main__:

class C
 |  Data descriptors defined here:
 |  
 |  a
 |      Hi, I'm A!
 |  
 |  b
 |      Hi, I'm B!

что я думаю должно быть в значительной степени то, что вы после.

Изменить: Теперь я понимаю, что мы можем, возможно, избежать необходимости передавать первый аргумент myprop вообще, потому что внутреннее имя не имеет значения. Если последующие вызовы myprop могут каким-либо образом связываться друг с другом, он может автоматически выбрать длинное и маловероятное внутреннее имя атрибута. Я уверен, что есть способы реализовать это, но я не уверен, что они того стоят.