【问题标题】:Docstring for variable变量的文档字符串
【发布时间】:2012-02-07 20:50:15
【问题描述】:

是否可以将文档字符串用于普通变量?例如,我有一个名为 t

的模块
def f():
    """f"""

l = lambda x: x
"""l"""

我愿意

>>> import t
>>> t.f.__doc__
'f'

但是

>>> t.l.__doc__
>>> 

示例类似于PEP 258 的(搜索“this is g”)。

【问题讨论】:

标签: python


【解决方案1】:

使用typing.Annotated 为变量提供文档字符串。

我最初写了一个答案(见下文),我说这是不可能的。早在 2012 年就是如此,但 Python 继续前进。今天,您可以为全局变量或类或实例的属性提供等效的文档字符串。您至少需要运行 Python 3.9 才能使其工作:

from __future__ import annotations
from typing import Annotated

Feet = Annotated[float, "feet"]
Seconds = Annotated[float, "seconds"]
MilesPerHour = Annotated[float, "miles per hour"]

day: Seconds = 86400
legal_limit: Annotated[MilesPerHour, "UK national limit for single carriageway"] = 60
current_speed: MilesPerHour

def speed(distance: Feet, time: Seconds) -> MilesPerHour:
    """Calculate speed as distance over time"""
    fps2mph = 3600 / 5280  # Feet per second to miles per hour
    return distance / time * fps2mph

您可以在运行时使用typing.get_type_hints() 访问注释:

Python 3.9.1 (default, Jan 19 2021, 09:36:39) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import calc
>>> from typing import get_type_hints
>>> hints = get_type_hints(calc, include_extras=True)
>>> hints
{'day': typing.Annotated[float, 'seconds'], 'legal_limit': typing.Annotated[float, 'miles per hour', 'UK national limit for single carriageway'], 'current_speed': typing.Annotated[float, 'miles per hour']}

使用声明变量的模块或类的提示提取有关变量的信息。注意嵌套时注释是如何组合的:

>>> hints['legal_limit'].__metadata__
('miles per hour', 'UK national limit for single carriageway')
>>> hints['day']
typing.Annotated[float, 'seconds']

它甚至适用于具有类型注释但尚未分配值的变量。如果我尝试引用 calc.current_speed 我会得到一个属性错误但我仍然可以访问它的元数据:

>>> hints['current_speed'].__metadata__
('miles per hour',)

模块的类型提示仅包括全局变量,要深入了解您需要在函数或类上再次调用get_type_hints()

>>> get_type_hints(calc.speed, include_extras=True)
{'distance': typing.Annotated[float, 'feet'], 'time': typing.Annotated[float, 'seconds'], 'return': typing.Annotated[float, 'miles per hour']}

到目前为止,我只知道一种工具可以使用typing.Annotated 来存储有关变量的文档,那就是 Pydantic。它比仅仅存储一个文档字符串稍微复杂一些,尽管它实际上需要一个pydantic.Field 的实例。这是一个例子:

from typing import Annotated
import typing_extensions
from pydantic import Field
from pydantic.main import BaseModel
from datetime import date

# TypeAlias is in typing_extensions for Python 3.9:
FirstName: typing_extensions.TypeAlias = Annotated[str, Field(
        description="The subject's first name", example="Linus"
    )]

class Subject(BaseModel):
    # Using an annotated type defined elsewhere:
    first_name: FirstName = ""

    # Documenting a field inline:
    last_name: Annotated[str, Field(
        description="The subject's last name", example="Torvalds"
    )] = ""

    # Traditional method without using Annotated
    # Field needs an extra argument for the default value
    date_of_birth: date = Field(
        ...,
        description="The subject's date of birth",
        example="1969-12-28",
    )

使用模型类:

>>> guido = Subject(first_name='Guido', last_name='van Rossum', date_of_birth=date(1956, 1, 31))
>>> print(guido)
first_name='Guido' last_name='van Rossum' date_of_birth=datetime.date(1956, 1, 31)

Pydantic 模型可以为您提供 JSON 模式:

>>> from pprint import pprint
>>> pprint(Subject.schema())
{'properties': {'date_of_birth': {'description': "The subject's date of birth",
                                  'example': '1969-12-28',
                                  'format': 'date',
                                  'title': 'Date Of Birth',
                                  'type': 'string'},
                'first_name': {'default': '',
                               'description': "The subject's first name",
                               'example': 'Linus',
                               'title': 'First Name',
                               'type': 'string'},
                'last_name': {'default': '',
                              'description': "The subject's last name",
                              'example': 'Torvalds',
                              'title': 'Last Name',
                              'type': 'string'}},
 'required': ['date_of_birth'],
 'title': 'Subject',
 'type': 'object'}
>>> 

如果您在 FastAPI 应用程序中使用此类,则 OpenApi 规范包含所有这三个从相关字段中获取的示例和描述。

这是当时正确但没有经受住时间考验的原始答案:

不,这是不可能的,如果可以的话,它也不会有用。

文档字符串始终是对象(模块、类或函数)的属性,与特定变量无关。

这意味着如果你可以这样做:

t = 42
t.__doc__ = "something"  # this raises AttributeError: '__doc__' is read-only

您将为整数 42 而不是变量 t 设置文档。一旦你重新绑定t,你就会丢失文档字符串。诸如字符串数量之类的不可变对象有时在不同用户之间共享一个对象,因此在此示例中,您实际上可能已经为整个程序中出现的所有 42 设置了文档字符串。

print(42 .__doc__) # would print "something" if the above worked!

对于可变对象,它不一定有害,但如果您重新绑定对象,它的用途仍然有限。

如果你想记录一个类的属性,那么使用类的文档字符串来描述它。

【讨论】:

  • @alexanderkuk:这个答案比我的要好。你应该接受它。
  • “不,如果可以的话,它就没有用了。”如果变量没有以这种方式实现的话。
  • 如果可以,那就没用了 -- 为什么不呢?如果我的模块使用符号,我应该如何记录它们?这样 pydoc 的 DATA 部分就有用了一半。
  • 它没有用的真正原因是文档字符串是对象的属性而不是特定上下文中的符号(例如,字典中的键,一个类或模块的“成员”)。这是一个有点缺陷的文档字符串设计,但是,当然,指出这一点并没有帮助或使这个答案不那么正确。一个实用的解决方案是将文档放入模块的文档字符串中。
  • 我觉得如果你可以将文档附加到成员上会很有用,但 Python 文档字符串的设计并不能真正让你这样做。
【解决方案2】:

Epydoc 允许 docstrings on variables:

虽然语言没有直接提供它们,但 Epydoc 支持 变量文档字符串:如果变量赋值语句是立即 后跟一个裸字符串文字,则该分配被视为 该变量的文档字符串。

例子:

class A:
    x = 22
    """Docstring for class variable A.x"""

    def __init__(self, a):
        self.y = a
        """Docstring for instance variable A.y"""

【讨论】:

  • 这应该是公认的答案。在 PyCharm 中也能完美运行。
  • @TheGodfather:你如何在 PyCharm 中使用它?对我不起作用:youtrack.jetbrains.com/issue/PY-35454
  • 不应该"""Docstring for instance variable A.y""" 结尾吗?
  • 我在 PyCharm 中使用过。只需将前面提到的 DocString 放在变量上,然后按 F1 即可显示它
  • 它也适用于 VS Code
【解决方案3】:

好吧,尽管 Python 不会将在全局定义之后立即定义的字符串视为变量的文档字符串,但 sphinx 会这样做,而且包含它们当然不是一个坏习惯。

debug = False
'''Set to True to turn on debugging mode. This enables opening IPython on 
exceptions.
'''

这里有一些代码会扫描模块并提取全局变量定义的名称、值和后面的文档字符串。

def GetVarDocs(fname):
    '''Read the module referenced in fname (often <module>.__file__) and return a
    dict with global variables, their value and the "docstring" that follows
    the definition of the variable
    '''
    import ast,os
    fname = os.path.splitext(fname)[0]+'.py' # convert .pyc to .py
    with open(fname, 'r') as f:
        fstr = f.read()
    d = {}
    key = None
    for node in ast.walk(ast.parse(fstr)):
        if isinstance(node,ast.Assign):
            key = node.targets[0].id
            d[key] = [node.value.id,'']
            continue
        elif isinstance(node,ast.Expr) and key:
            d[key][1] = node.value.s.strip()
        key = None
    return d

【讨论】:

    【解决方案4】:

    Sphinx 具有用于记录属性的内置语法(即不是 @duncan 描述的值)。例子:

    #: This is module attribute
    x = 42
    
    class MyClass:
    
        #: This is a class attribute
        y = 43
    

    您可以在 Sphinx 文档中阅读更多内容:https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autoattribute

    ...或在其他问题中:How to document a module constant in Python?

    【讨论】:

      【解决方案5】:

      一些 python 文档脚本具有可在模块/类文档字符串中用于记录 var 的符号。

      例如对于 spinx,您可以使用 :var 和 :ivar。看到这个document(大约一半)。

      【讨论】:

        【解决方案6】:

        不,据我所知,您只能对模块、(lambda 和“普通”)函数和类执行此操作。其他对象,即使是可变对象,也会继承其类的文档字符串,并在您尝试更改时引发 AttributeError

        >>> a = {}
        >>> a.__doc__ = "hello"
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        AttributeError: 'dict' object attribute '__doc__' is read-only
        

        (您的第二个示例是有效的 Python,但字符串 """l""" 不执行任何操作。它被生成、评估和丢弃。)

        【讨论】:

          【解决方案7】:

          要添加到福特关于 Epydoc 的回答,请注意 PyCharm 还将使用字符串文字作为类中变量的文档:

          class Fields_Obj:
              DefaultValue=None
              """Get/set the default value of the data field"""
          

          【讨论】:

          • 文档不能与声明在同一行,这太糟糕了。这将允许他们轻松地重新排列(使用行排序命令)并且不会丢失他们的文档。
          • @LS:你可以用分号分隔它们。 DefaultValue = None; """Get/set the default value of the data field"""。我不知道 Epydoc 或 PyCharm 是否接受。 Epydoc 还将#: 后跟的赋值视为变量文档字符串。 x = 22 #: docstring for x
          • 不幸的是,在 PyCharm (2019.1) 中,这仅在为属性分配值时才有效。如果你只是添加类型注释而不赋值,则文档字符串不会被拾取。
          【解决方案8】:

          很多答案都假设您希望它离线使用并指向 sphinx 或 Epydoc。

          但是,如果您希望它在运行时使用,答案是不可能将属性添加到另一个属性。因此,您不能将文档字符串附加到变量。

          当你这样做时:

          a = True
          print(a.__doc__)
          

          您将获得 bool 类的文档字符串。 在我的应用程序中,我需要它用于插件。我所做的是使用关联的变量/属性。

          类似这样的:

          a = True
          _help_a = "help for a variable"
          

          因为这看起来很难看,我实际使用的是语法宏(看看macropy 模块)。代码如下所示:

          with document:
              a = True
              """ help for a variable """
          

          我解释了整个想法here

          【讨论】:

            【解决方案9】:

            属性可以有文档字符串!这涵盖了记录实例变量的最常见用例。

            class A:
                def __init__(self):
                    self._x = 22
            
                @property
                def x(self):
                    "document x"
                    return self._x
            
                @x.setter
                def x(self, value):
                    self._x = value
            
            A.x.__doc__
            

            【讨论】:

            • 是的。但是添加两个方法只是为一个变量添加一个文档字符串似乎不是一个合理的方法。
            • 我同意,但如果您需要一个实际生成 doc 而不仅仅是生成 sphinx 文档的解决方案,那么这可能是一个不错的选择。
            • 格式化失败:我指的是上面的 __doc__,而不是粗体版本。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-05-05
            • 1970-01-01
            • 2012-12-14
            • 1970-01-01
            • 1970-01-01
            • 2017-10-28
            相关资源
            最近更新 更多