器→工具, 编程语言

PEP 257:Docstring书写规范

钱魏Way · · 1,400 次浏览

Python编码规范PEP 8文章中提到了PEP 257,Docstring书写规范。什么是Docstring?简单的说Docstring是一种文档字符串,用于解释构造的作用。我们在函数、类或方法中将它放在首位来描述其作用。我们用三个单引号或双引号来声明docstring。

以下为官方PEP 257 — Docstring Conventions翻译的内容。

摘要

本篇PEP文档主要对Python函数Docstring的语义和规范进行了相关阐述。

基本原理

PEP的目标是让程序员写出的Python Docstring高度结构化、规范化:文档字符串应该包含什么,如何书写(在文档字符里避免使用标记语法的情况下)。 PEP中包含的是约定俗成的东西,不是定律或严苛的语法。

一个通用的公约给编程提供了可维护性,清晰性,一致性,同时也提供了良好的编程习惯的基础。但它从来不会强迫你去按照它的标准做你不喜欢的事情。这就是Python!”

—Tim Peters on comp.lang.python, 2001-06-16

如果你不按约定编写代码,最多导致不够美观。但有些软件代码的编写必须按照约定来(比如 Docutils docstring 的处理系统)。总而言之,遵守约定,能给你带来最好的结果。

详细说明

什么是Docstring?

Docstring是模块、类、方法中的第一个声明性描述说明。比如docstring变成 __doc__ 类的special attribute of that object.

所有的modules一般都有Docstring,模块中的类和方法也应该有Docstring。公共方法 (包含 __init__ 构造器的方法) 也应该有Docstring。一个包的描述信息一般在包目录中文件__init__.py的Docstring中。

字符串类说明信息在Python代码中随处可见,通常用称为文档说明的一部分。这些信息不会被Python字节码编译器识别到,也不能被运行时对象属性访问。但是,有两类附加的docstring能被软件工具提取出来。

  • 在一个模块、类或者__init__ 方法中一开始就出现的字符串描述信息,我们称之为 “attribute docstrings”
  • 紧跟在一个 docstring 后面的字符串描述信息,被称为 “additional docstrings”

请参考 PEP 258 , “Docutils Design Specification”,里面有关于”attribute docstrings”和”additional docstrings”的详细说明。

下面是三种情况下,Docstring的一般写法:

  • 正常情况下: “””三个双引号””” (单纯用三个双引号引起来)
  • 如果有反斜线: r”””raw triple double quotes””” (在开头加字幕r)
  • 如果使用统一字符编码标准:u”””Unicode triple-quoted strings””” . (在开头加字幕u)

一共有两种Docstring,单行的和多行的:

单行Docstring

单行Docstring用于显而易见的情况,将说明描述字符串写入一行即可。例如:

def kos_root():
    """Return the pathname of the KOS root directory."""
    global _kos_root
    if _kos_root: return _kos_root
    ...

注意:

  • 即使是单行也要使用三引号,为的以后可以轻松扩展它。
  • 关闭的引号应该和开始的引号在同一行,对于单行,这样看起来更好。
  • Docstring的开始和结束不空行。
  • 描述函数或方法的作用时应该使用命令式短语(“Do this”, “Return that”)而不是作为一段描述。比如,不要写“Returns the pathname …”。
  • 单行Docstring不应该重申函数或方法的参数(因为可以通过内省得到)。 不要像这样:
def function(a, b):
    """function(a, b) -> list"""

上面的这种类型的文档说明只适用于C函数(例如内建函数)这样的无法内省的函数。然而,return值的类型无法由内省决定,因此这需要被注意。对于这种Docstring的首选形式是:

def function(a, b):
  """Do X and return a list."""

多行Docstring

多行Docstring的开始是一段摘要,就像单行Docstring一样,接下来是一个空行,然后是更多详细描述。摘要可以被自动索引工具所使用;关键在于,摘要适用于一行,并且使用一个空行与剩下的Docstring进行区分。摘要可以与开始引号在同一行或另起一行。整个文档字字符串的缩进应该与第一行的引号相同(参照下面的示例)。

在类的所有Docstring(一行或多行)之后插入一个空白行——通常来说,类的方法是通过一个空白行彼此分开的,所以Docstring需要一个空行来和类的第一个方法分开。

脚本(独立程序)的Docstring应该可以用作其“使用方法”消息,当脚本被调用时使用不正确或缺少的参数(或者可能使用“-h”选项,用于“帮助”)进行打印。这样一个Docstring应该记录脚本的功能和命令行语法,环境变量和文件。使用方法信息可以是相当详细的(随便占个几屏),并且应该足以指导新用户正确使用命令,以及高级用户也可以完整快速的索引到全部选项和参数。

模块的Docstring通常应列出由模块导出的类,异常和函数(以及任何其他对象),其中包含每个模块的一行摘要。(这些摘要通常比对象的Docstring中的摘要行提供的细节更少)。软件包的Docstring(即软件包的__init__.py模块的Docstring)也应该列出软件包导出的模块和子包。

函数或方法的Docstring应该总结其行为并记录其参数,返回值,副作用,引发的异常以及可以调用的限制(如果适用的话)。 应该指出可选参数。 应该记录关键字参数是否是接口的一部分。

类的Docstring应该总结其行为并列出公共方法和实例变量。如果该类旨在被子类化,并且有一个子类的附加接口,则该接口应该单独列出(在Docstring中)。类构造函数应该在Docstring中记录其__init__方法。私有方法应该由他们自己的Docstring记录。

如果一个类对另一个类进行子类化,并且其行为大部分从该类继承,则其Docstring应该提及这一点,并总结出差异。 使用动词“override”表示子类方法替换超类方法,不调用超类方法; 使用动词“extend”来表示一个子类方法调用超类方法(除了自己的行为)。

在运行文本时,请勿使用Emacs习俗来对函数或方法的参数进行首字母大写。Python区分大小写,参数名称可以用于关键字参数,因此Docstring应该记录正确的参数名称。最好在单独的行上列出每个参数。 例如:

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
    ...

除非整个Docstring串符合一行,否则将结束引号单独起一行。 这样可以支持使用Emacs的fill-paragraph命令。

处理Docstring缩进

Docstring处理工具将从Docstring的第二行和另外一行剥离均匀的缩进量,等于第一行之后的所有非空白行的最小缩进。文档字符串的第一行中的任何缩进(即,直到第一个换行符)都是微不足道的,并被删除。Docstring中后续行的相对缩进被保留。 应从文本字符串的开头和结尾删除空白行。

由于代码比文字表述更准确,这里是算法的实现:

ef trim(docstring):
    if not docstring:
        return ''
    # Convert tabs to spaces (following the normal Python rules)
    # and split into a list of lines:
    lines = docstring.expandtabs().splitlines()
    # Determine minimum indentation (first line doesn't count):
    indent = sys.maxint
    for line in lines[1:]:
        stripped = line.lstrip()
        if stripped:
            indent = min(indent, len(line) - len(stripped))
    # Remove indentation (first line is special):
    trimmed = [lines[0].strip()]
    if indent < sys.maxint:
        for line in lines[1:]:
            trimmed.append(line[indent:].rstrip())
    # Strip off trailing and leading blank lines:
    while trimmed and not trimmed[-1]:
        trimmed.pop()
    while trimmed and not trimmed[0]:
        trimmed.pop(0)
    # Return a single string:
    return '\n'.join(trimmed)

此示例中的Docstring包含两个换行符,因此为3行长。 第一行和最后一行是空白的:

def foo():
    """
    This is the second line of the docstring.
    """

举例说明:

>>> print repr(foo.__doc__)
'\n    This is the second line of the docstring.\n    '
>>> foo.__doc__.splitlines()
['', '    This is the second line of the docstring.', '    ']
>>> trim(foo.__doc__)
'This is the second line of the docstring.'

经过裁剪处理后,下面两段代码的Docstring是等价的:

def foo():
    """A multi-line
    docstring.
    """

def bar():
    """
    A multi-line
    docstring.
    """

参考和补充说明

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注