如何为带参数的方法编写文档?

243
如何使用Python的文档字符串记录带参数的方法? PEP 257 给出了以下示例:
def complex(real=0.0, imag=0.0):
    """Form a complex number.

    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0)

    """
    if imag == 0.0 and real == 0.0: return complex_zero
    ...

这是大多数Python开发者使用的惯例吗?

Keyword arguments:
<parameter name> -- Definition (default value if any)

我本来期望得到更正式的内容,比如:

def complex(real=0.0, imag=0.0):
    """Form a complex number.

    @param: real The real part (default 0.0)
    @param: imag The imaginary part (default 0.0)

    """
    if imag == 0.0 and real == 0.0: return complex_zero
    ...

环境:Python 2.7.1


2
你读过PEP 257吗?http://www.python.org/dev/peps/pep-0257/ - NPE
2
有几个“标准”可供选择,但从实际角度出发,特别是如果您喜欢正式的东西,我建议使用sphinx。它在Pycharm中的集成使生成良好结构化的文档字符串变得轻松无痛。在我看来,这是一个不错的选择。 - j-i-l
9个回答

273

由于文档字符串是自由格式的,因此它取决于您用什么来解析代码以生成 API 文档。

我建议熟悉Sphinx 标记,因为它被广泛使用并正在成为记录 Python 项目的事实标准,部分原因是由于出色的readthedocs.org服务。要将 Sphinx 文档中的示例转换为 Python 代码段,可以引用如下释义

def send_message(sender, recipient, message_body, priority=1) -> int:
   """
   Send a message to a recipient.

   :param str sender: The person sending the message
   :param str recipient: The recipient of the message
   :param str message_body: The body of the message
   :param priority: The priority of the message, can be a number 1-5
   :type priority: integer or None
   :return: the message id
   :rtype: int
   :raises ValueError: if the message_body exceeds 160 characters
   :raises TypeError: if the message_body is not a basestring
   """

这种标记语言支持文档之间的交叉引用等功能。请注意,Sphinx文档使用例如:py:attr:的方式,而当您从源代码进行文档编写时,您只需使用:attr:即可。

当然,还有其他工具可以用来记录API。例如经典的Doxygen使用\param命令,但这些命令并非专门设计用于类似Sphinx这样记录Python代码的工具。

请注意,这里有一个类似的问题,并且有一个相似的回答...


20
这是PyCharm默认使用的注释自动生成样式。 - Josiah Yoder
我不知道为什么他们不建议在每个参数类型对之间放置空行。这样太丑了。 - Ereghard
2
@Ereghard 紧凑的代码总是更好的,随着时间的推移,你会逐渐习惯它。 - alper
1
请更新以使用类型提示。Sphinx是否可以从参数声明(例如 sender:str)中抓取类型提示,以便不必在“:param sender”行上重复类型? - chrisinmtown
请更新以使用类型提示。Sphinx能否从参数声明中获取类型提示(例如,sender: str),以便在:param sender行上不必重复类型? - chrisinmtown
显示剩余2条评论

121
根据我的经验,NumPy文档字符串约定(PEP257的超集)是最广泛使用且得到支持的工具(例如Sphinx)所遵循的约定之一。

一个例子:

Parameters
----------
x : type
    Description of parameter `x`.

2
这更接近我所期望的。不幸的是,我选择了普通的PEP 257并添加了自己的约定(以失去自动生成的HTML / PDF文档为代价)。但是,下次我会选择这个解决方案。谢谢。 - David Andreoletti
5
当我试图处理你建议的docstring时,Sphinx会报错SEVERE: Unexpected section title。你知道有什么办法让Sphinx对此更满意吗? - Brandon Rhodes
@BrandonRhodes 这个链接讨论了如何在Sphinx中使用这些约定:https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt - Vladimir Keleshev
3
实际上,在“Description”之前缺少一个空格。我查看了numpy文档,因为我立即注意到并想到“等一下,为什么是三个空格?那很奇怪。谁会用三个空格?” - Zelphir Kaltstahl
9
在问题提出时,这可能是最好的答案之一,但我认为到现在为止(2017年末),Sphinx已经脱颖而出。 - Alex L
1
@BrandonRhodes评论中的链接现在已经失效。请使用以下链接:https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard - B. Fuller

39

约定:

工具:


更新:自Python 3.5起,您可以使用类型提示,这是一种紧凑且机器可读的语法:

from typing import Dict, Union

def foo(i: int, d: Dict[str, Union[str, int]]) -> int:
    """
    Explanation: this function takes two arguments: `i` and `d`.
    `i` is annotated simply as `int`. `d` is a dictionary with `str` keys
    and values that can be either `str` or `int`.

    The return type is `int`.

    """

这种语法的主要优点是它由语言定义,且没有歧义,因此像PyCharm这样的工具可以轻松地利用它。


14
虽然这个回答现在是最受赞的,但以上PEP中都没有提供一种指定方法参数类型的约定。 - koriander

12

Python文档字符串是自由格式的,您可以以任何方式记录它。

示例:

def mymethod(self, foo, bars):
    """
    Does neat stuff!
    Parameters:
      foo - a foo of type FooType to bar with.
      bars - The list of bars
    """

现在,有一些约定,但Python并没有强制执行任何规则。一些项目有自己的约定。一些处理文档字符串的工具也遵循特定的约定。


8

5
主流方式,如其他答案已经指出的那样,可能会采用Sphinx 方法,以便稍后可以使用Sphinx生成这些漂亮的文档。
话虽如此,我个人有时候会采用内联注释样式。
def complex(  # Form a complex number
        real=0.0,  # the real part (default 0.0)
        imag=0.0  # the imaginary part (default 0.0)
        ):  # Returns a complex number.
    """Form a complex number.

    I may still use the mainstream docstring notation,
    if I foresee a need to use some other tools
    to generate an HTML online doc later
    """
    if imag == 0.0 and real == 0.0:
        return complex_zero
    other_code()

这里再举一个例子,其中一些微小的细节被内联记录:

def foo(  # Note that how I use the parenthesis rather than backslash "\"
          # to natually break the function definition into multiple lines.
        a_very_long_parameter_name,
            # The "inline" text does not really have to be at same line,
            # when your parameter name is very long.
            # Besides, you can use this way to have multiple lines doc too.
            # The one extra level indentation here natually matches the
            # original Python indentation style.
            #
            # This parameter represents blah blah
            # blah blah
            # blah blah
        param_b,  # Some description about parameter B.
            # Some more description about parameter B.
            # As you probably noticed, the vertical alignment of pound sign
            # is less a concern IMHO, as long as your docs are intuitively
            # readable.
        last_param,  # As a side note, you can use an optional comma for
                     # your last parameter, as you can do in multi-line list
                     # or dict declaration.
        ):  # So this ending parenthesis occupying its own line provides a
            # perfect chance to use inline doc to document the return value,
            # despite of its unhappy face appearance. :)
    pass

好处(正如@mark-horvath在另一条评论中指出的)是:
  • 最重要的是,参数及其文档始终在一起,这带来以下好处:
  • 减少输入量(无需重复变量名)
  • 更易于维护以更改/删除变量。在重命名某个参数后,永远不会有一些孤立的参数文档段落。
  • 更容易找到缺失的注释。

现在,有些人可能认为这种风格看起来“丑陋”。但我会说“丑陋”是一个主观的词。一个更中立的方式是说,这种风格不是主流的,所以它可能看起来对你来说不太熟悉,因此不太舒适。再次强调,“舒适”也是一个主观的词。但关键是,上述所有好处都是客观存在的。如果您遵循标准方式,则无法实现它们。

希望将来有一天会有一种文档生成器工具,它也可以使用这种内联样式。这将推动采用。

PS:这个答案源于我自己喜欢在合适的时候使用内联注释的偏好。我也使用相同的内联样式来记录字典


1
我认为这是一种不错的方法。其中一个很大的优点是参数和其文档位于同一位置,如果你重构事物/更改类型注释等,这将非常有帮助-您无需在同步两个不同的列表中保存任何东西。希望能够建立一种使用此方式进行文档生成的工具! - jcdude
我个人喜欢这种风格,但是我的IDE不会在工具提示/自动完成中显示这些注释 :( - LogicDaemon

3

Sphinx Python 3 类型最小可运行示例

为了更加具体化其他答案所说的内容:

build.sh

sphinx-build . out

conf.py

import os
import sys
sys.path.insert(0, os.path.abspath('.'))
extensions = [ 'sphinx.ext.autodoc' ]
autodoc_default_options = {
    'members': True,
    'show-inheritance': True,
}
autodoc_typehints = "description"

index.rst

.. automodule:: main

main.py

class MyClass:
    """
    This class does that.
    """
    def __init__(self, i):
        self.i = i

def do_this(parameter1: int, parameter2: MyClass) -> int:
   """
   This function does this.

   :param parameter1: my favorite int
   :param parameter2: my favorite class
   :return: what it returns
   """
   return parameter1 + parameter2.i

requirements.txt

Sphinx==6.1.3

输出到 out/index.html

enter image description here

如果我们移除:

autodoc_typehints = "description"

在函数签名中显示打字类型:

enter image description here

预输入等效

以下代码将产生与使用autodoc_typehints = "description"版本相同的输出,但会稍微重复一些参数名称:

def do_this(parameter1, parameter2):
   """
   This function does this.

   :param parameter1: my favorite int
   :type parameter1: int
   :param parameter2: my favorite class
   :type parameter2: MyClass
   :return: what it returns
   :rtype: int
   """
   return parameter1 + parameter2.i

梦想:参数文档字符串

我们不得不为每个:param argumentname:重新输入参数名称,这是很糟糕的。目前似乎没有一个好的解决方案。以下是一些未来可能解决这个问题的方法:

  • Sphinx可以为参数添加#:文档注释。如How to show instance attributes in sphinx doc?所述,该Sphinx扩展已经适用于实例属性。

    class MyClass:
        def __init__(self, par1: int):
            #: My favorite attribute!
            self.var1: int = par1 
    

    那么为什么不给我们:

    def do_this(
        #: my favorite int
        parameter1: int,
        #: my favorite class
        parameter2: MyClass
    ) -> int:
    
  • Python终于可以原生支持参数文档字符串了!

    def do_this(
        parameter1: int,
        """
        my favorite int
        """
    
        parameter2: MyClass
        """
        my favorite class
        """
    ) -> int:
    

TODO 找到这些功能请求的需求!

同样的需求也在此处提及:https://dev59.com/MWox5IYBdhLWcg3weUCe#39581355

其他有用的打字相关事项

已在Python 3.10、Ubuntu 22.10上测试。


3

在 type-hints 回答的基础上 (https://dev59.com/MWox5IYBdhLWcg3weUCe#9195565),提供了一种更好的结构化方式来记录参数类型,还有一种结构化的方式来记录参数类型和描述:

def copy_net(
    infile: (str, 'The name of the file to send'),
    host: (str, 'The host to send the file to'),
    port: (int, 'The port to connect to')):

    pass

示例来源: https://pypi.org/project/autocommand/


3
这是官方语法吗?它非常有用,但我在官方文档/PEP中找不到它... - Ofri Raviv
3
我也想知道,是否有相应的 PEP。 - dreamflasher
4
这似乎意味着“一个由两个元素组成的元组:一个字符串和一个typing.Literal字面字符串,其文本为'The name of the file to send'”,这也是pyright的解释。虽然可能很好,但除非未来明确采用PEP允许这样做,否则我认为这不是正确的答案。 - Ivan Vučica

-3

文档字符串仅在交互式环境(例如Python shell)中有用。当记录不会被交互使用的对象时(例如内部对象、框架回调),您可以使用常规注释。这是我用于悬挂缩进注释的样式,每个注释都在自己的行上,以便您知道该注释适用于哪个项目:

def Recomputate \
  (
    TheRotaryGyrator,
      # the rotary gyrator to operate on
    Computrons,
      # the computrons to perform the recomputation with
    Forthwith,
      # whether to recomputate forthwith or at one's leisure
  ) :
  # recomputates the specified rotary gyrator with
  # the desired computrons.
  ...
#end Recomputate

你不能使用文档字符串来完成这种操作。


2
丑陋吗?有趣的想法...也是。 - David
3
内联变量注释非常明智,可以减少打字(无需重复变量名称),在更改/删除变量时易于维护...更容易找到缺少的注释。建议在函数签名下方添加适当的文档字符串。+1 - Mark Horvath
这并不是一种好的文档实践。如果你像这样对你的程序包进行注释,那么使用 PyCharm 的用户下载后将无法在不访问你的文档(无法通过任何软件生成)的情况下查看每个参数的作用。除非你自己编写文档。这就是 OP 要求在 docstring 中指定它的原因。很抱歉回复晚了。 - user5147563
8
太糟糕了。 - Michael Walters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接