如何在 Python 中记录类属性?

Posted

技术标签:

【中文标题】如何在 Python 中记录类属性?【英文标题】:How to document class attributes in Python? 【发布时间】:2011-03-04 08:02:17 【问题描述】:

我正在编写一个轻量级类,其属性旨在公开访问,并且仅在特定实例化中有时会被覆盖。就此而言,Python 语言中没有为类属性或任何类型的属性创建文档字符串的规定。记录这些属性的预期和支持方式是什么?目前我正在做这样的事情:

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)

这将导致类的文档字符串包含初始标准文档字符串部分,以及通过对__doc__ 的扩充分配为每个属性添加的行。

虽然docstring style guidelines 中似乎没有明确禁止这种风格,但也没有提到它作为一个选项。这里的优点是它提供了一种在属性定义旁边记录属性的方法,同时仍然创建一个可展示的类文档字符串,并避免编写重复来自文档字符串的信息的 cmets。我仍然有点恼火,我实际上必须写两次属性;我正在考虑使用 docstring 中值的字符串表示形式,至少可以避免默认值的重复。

这是否严重违反了临时社区公约?没事吧?有没有更好的办法?例如,可以创建一个包含属性值和文档字符串的字典,然后将内容添加到类__dict__ 和类声明末尾的文档字符串;这将减少两次键入属性名称和值的需要。 编辑:我认为最后一个想法实际上是不可能的,至少在没有从数据动态构建整个类的情况下是不可能的,这似乎是一个非常糟糕的想法,除非有其他理由这样做。

我对python还很陌生,还在研究编码风格的细节,所以也欢迎无关的批评。

【问题讨论】:

如果您正在寻找一种记录 Django 模型属性的方法,这可能会有所帮助:djangosnippets.org/snippets/2533 How to document fields and properties in Python? 的副本,其中包含不同的解决方案。 我不明白为什么这是基于意见的。 Python 专门记录了它在 PEP 中可接受的约定。有不同的 Python 源工具可以提取格式正确的文档。事实上,Python 实际上有一个PEP 257 中提到的attribute doc string,它并不为人所知,似乎很难找到它可以回答 OPs 的问题,并且得到一些源工具的支持。这不是意见。这是事实,也是语言的一部分,几乎正是 OP 想要的。 【参考方案1】:

为避免混淆:property 一词在 python 中有一个specific meaning。你所说的就是我们所说的class attributes。因为他们总是通过他们的班级被执行,我发现在班级的文档字符串中记录他们是有意义的。像这样的:

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 中,文档字符串是它们记录的对象的实际成员,而不仅仅是源代码注释。由于类属性变量本身不是对象,而是对对象的引用,因此它们无法保存自己的文档字符串。我想您可以为引用中的文档字符串提供一个案例,也许可以描述“这里应该有什么”而不是“这里实际有什么”,但我发现在包含的类文档字符串中这样做很容易。

【讨论】:

我想在大多数情况下这很好,因为属性 - 感谢术语更正 - 已经足够简洁地声明它们可以在类声明的开头进行分组,而不会使翻转不切实际来回阅读文档和默认值或更新文档和/或默认值的两个实例。 另请注意,我的示例将导致属性的文档出现在类的文档字符串中。我实际上更愿意将文档放在属性本身的文档字符串中,但这不适用于大多数内置类型。 是的,我最初的想法是声明例如flight_speed = 691; flight_speed.__doc__ = "blah blah"。我认为这就是您在编辑中提到的内容。不幸的是,这不适用于(大多数?)内置类型的实例化(例如该示例中的int)。它确实适用于用户定义类型的实例化。 =========== 实际上有一个 PEP(对不起,忘记数字)建议为类/模块属性添加文档字符串,但被拒绝了,因为他们无法找到一种明确的方法文档字符串是用于前面还是后面的属性。 那么如果它们是实例属性呢?仍然在类文档字符串中记录还是什么? @intuited 是这个 PEP 吗? legacy.python.org/dev/peps/pep-0224【参考方案2】:

您引用了 PEP257: Docstring Conventions,在 What is a docstring 部分中指出:

Python 代码中其他地方出现的字符串文字也可以作为文档。它们不被 Python 字节码编译器识别,也不能作为运行时对象属性访问(即未分配给 __doc__),但软件工具可以提取两种类型的额外文档字符串:

在模块、类或 __init__ 方法的顶层简单赋值之后立即出现的字符串文字称为“属性文档字符串”。

这在 PEP 258: Attribute docstrings 中有更详细的解释。 如上所述ʇsәɹoɈ。 属性不是可以拥有 __doc__ 的对象,因此它们不会出现在 help() 或 pydoc 中。这些文档字符串只能用于生成的文档。

它们在 Sphinx 中与directive autoattribute 一起使用

Sphinx 可以在赋值之前的一行上使用 cmets,也可以在赋值之后的特殊注释或定义之后的文档字符串中使用 cmets,这将被自动记录。

【讨论】:

jedi-vim 插件也可以识别属性文档字符串。 我不知道这是什么时候引入的,但是 Sphinx 1.2.2 似乎在生成的文档中包含了属性文档字符串。 谢谢@jochen,我更新了我的答案。 请注意,PEP 258 被拒绝。拒绝通知指出:“虽然这可能是现在独立的 docutils 的有趣设计文档,但它不再计划包含在标准库中。” VS Code 的 Pylance 自 2021.7.6(2021-07 发布)起支持属性文档字符串:github.com/microsoft/pylance-release/issues/1576【参考方案3】:

你可以滥用属性来达到这个效果。属性包含一个getter、一个setter、一个deleter、和一个docstring。天真地,这会变得非常冗长:

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

那么你将拥有一个属于 C.x 的文档字符串:

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!

然后,调用 Pythons 交互式 help 将给出:

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 的后续调用可以以某种方式相互通信,它可以自动决定一个长且不太可能的内部属性名称。我确信有办法实现这一点,但我不确定它们是否值得。

【讨论】:

有趣的解决方案,但除非 Python 在创建一个函数并调用它只是为了访问一个属性是不必要的开销。我知道 OP 正在询问记录属性,但添加所有这些(尤其是最后一个带有嵌套函数 -_-)的内容太多了。 @rbaleksandar 你是对的。我在 8 多年前发布了这个,实践表明我自己从来没有这样做过。但是,我仍然认为它显示了一些有关具有文档字符串的属性的信息,这可能会引起某些人的兴趣。【参考方案4】:

其他答案非常已过时。 PEP-224(在 Python 2.1 中可用!)描述了如何将文档字符串用于属性。奇怪的是,它们出现在 属性之后。

class C:
    "class C doc-string"

    a = 1
    "attribute C.a doc-string (1)"

    b = 2
    "attribute C.b doc-string (2)"

它也适用于这样的类型注释:

class C:
    "class C doc-string"

    a: int
    "attribute C.a doc-string (1)"

    b: str
    "attribute C.b doc-string (2)"

VSCode 支持显示这些。

【讨论】:

PEP 224 被拒绝,但这个答案仍然有用,因为 Python 生态系统中的许多工具都支持这种定义属性文档字符串的方式。 哦,是的,我错过了。然而,这只是一个约定,尽管 Guido 说他不喜欢它,但似乎得到了广泛的支持,因为字符串出现在文档之后(这很奇怪 tbf)。例如。 here、Sphinx supports it 和 Pylance 支持它。这是事实上的标准。 mkdocstrings也支持。 此行为也记录在 PEP 257 中,因此它标准的一部分:“在模块、类的顶层简单赋值后立即发生的字符串文字,或 `__init__`` 方法称为“属性文档字符串”。”

以上是关于如何在 Python 中记录类属性?的主要内容,如果未能解决你的问题,请参考以下文章

python 类属性和类方法

Python基础班每日整理

如何使用 phpDocumentor 在 PHP 5 中记录类属性

如何在 python 抽象类中创建抽象属性

python中的单例设计模式

Python中类属性和实例属性的区别