Python 学习笔记 #3 —— Docstring 风格

Posted on 2020-04-26 19:20 in CS

PEP 257 -- Docstring Conventions 原文链接

item detail
PEP 257
Title Docstring Conventions
Author David Goodger , Guido van Rossum
Status Active
Type Informational
Created 29-May-2001
Post-History 13-Jun-2001

摘要

本文描述了 Python docstrings 的语法和惯例。

基本原理

本文的目的是在 high-level 的层次对 docstrings 结构进行标准化:应该包含哪些内容,以及如何表述(docstrings 内部不需要任何的标记性语法)。本文的内容是惯例,而不是严格的语法或法律。

"A universal convention supplies all of maintainability, clarity, consistency, and a foundation for good programming habits too. What it doesn't do is insist that you follow it against your will. That's Python!"

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

如果你违法了这些惯例,最差的结果也只不过是你的作品看起来比较丑陋。但是一些软件(比如 Docutils 系统)会感知到 docstrings,所以遵守这些惯例可以让你获得最好的结果。

标准

Docstrings 是什么

docstrings 是一个字符串,是 module, function, class, method 中的第一个语句,这些字符会变成该 object 的特殊属性 __doc__

所有的 module 都应该有 docstrings,module 中所有可以导出的 function 和 class 也都应该有 docstrings。class 的 public method(包括 __init__ 构造器)也应该有 docstrings。一个 package 可以在自己目录下面 __init__.py 文件的 docstrings 中进行描述。

Python 文件中其他位置的字符串也可以成为文档的一部分,它们无法被 Python 的字节码编译器识别,runtime 的时候也无法访问(也就是说,没有赋值给 __doc__ 属性),但是有两种类型的 docstrings 可以被软件工具识别出来:

  1. 在 module, class, __init__ 方法的顶层,简单赋值语句后面的字符串,叫做 “attribute docstrings”
  2. 在 docstrings 之后紧跟着出现的字符串,叫做 “additional docstrings”

关于这两种 docstrings 的详细描述请参考 PEP258 "Docutils Design Specification"

为了保持一致性,永远使用三个双引号 """triple double quotes""" 包围 docstrings。如果在 docstrings 中使用到了反斜线,请使用 r"""raw triple double quotes""",对于使用 Unicode 字符的情况,请使用 u"""Unicode triple quoted string"""

docstrings 有两种形式:单行、多行。

单行 Docstrings

单行 docstrings 显而易见,就是只有一行。举例,

1
2
3
4
5
def kos_root():
    """Return the pathname of the KOS root directory."""
    global _kos_root
    if _kos_root: return _kos_root
    ...

注意:

  • 即使是单行的情况,仍然使用三双引号,方便以后扩展成多行的情况
  • 开头和结尾的引号在同一行,这样看起来要美观一些
  • docstrings 前后没有空行
  • docstrings 用一个以句号结尾的短语,它用命令性的方式规定了 function/method 的效果(比如 “Do this”,“Return that”),而不是描述性的方式(比如,不要写成这样 "Returns the pathname...")
  • 单行的 docstrings 不应该是 function/method 的参数的重新声明(可以通过内省实现),不要写成这样

    1
    2
    def function(a, b):
    """function(a, b) -> list"""
    

    这种类型的 docstrings 只适合于 C 函数(比如内建函数),因为 C 没有内省机制。然而内省无法决定返回值的类型,所以要在 docstrings 中进行说明。所以 docstrings 应该优先选择下面的方式,

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

多行 Docstrings

多行 docstrings 的结构分为 2 段,第一段是一个类似于单行 docstrings 的总结行,第二段是更详细的描述,两段之间用一个空行隔开。总结行可能会被自动化索引工具使用到,所以让它的长度保持在一行内,并且用空行和其他部分隔开非常重要。总结行可以放在开头引号的同一行,也可以放到下一行。整个 docstrings 和引号的缩进保持一致(见下面的例子)。

class 的 docstrings 的后面要插入一(多)个空行。一般来说 class 的 methods 之间会通过一个空行进行隔离,docstrings 也需要一个空行来和第一个 method 进行隔离。

一个脚本(作为一个单独的程序)的 docstrings 应该可以当作 Usage message 来使用,当使用不正确的参数(或者是表示 help 的 -h 参数)调用脚本时打印出这些内容。这种 docstrings 应该包含脚本的功能、命令行语法、环境变量、文件等信息。Usage message 可以非常详细(内容长达几个全屏),达到可以指导一个新用户正确使用本脚本命令,这个信息也可以作为高级用户查询所有选项和参数的快速参考。

一个 module 的 docstrings 应该列出所有可以被导出的 class,exception 和 function 以及其他 objects,每个对象都有一个单行的总结性描述(这些总结比 docstrings 的总结行更简洁)。

一个 package 的 docstrings(比如,__init__.py 的 docstrings)也应该列出可以导出的 module 和 subpackage。

一个 function/method 的 docstrings 应该总结它的行为,描述它的参数,返回值,副作用,抛出的 exception,调用时的约束。同时应该指出可选参数,无论 keyword 参数是不是接口的一部分,都应该进行描述。

一个 class 的 docstrings 应该总结它的行为,列出 public method 和 instance varibale。如果它本身的设计目的是子类化,并且针对 subclass 留有额外的接口,那么这个额外接口应该在 docstrings 中单独列出来。构造器应该在 __init__ 方法的 docstrings 中描述,其他的 method 都在自己的 docstrings 中进行描述。

如果一个 subclass 的大部分行为都继承自另外一个 class,那么它的 docstrings 应该提到这一点并且总结两者的不同之处。用动词 override 来说明 subclass 的方法重写了 superclass 的同名方法;用动词 extend 来表示 subclass 的方法调用了 superclass 的同名方法,并且添加了自己额外的功能。

在 docstrings 中涉及到 function/method 的参数时不要用 Emacs 的大写惯例。Python 对大小写敏感而且参数的名字可以用作是 keyword 参数,所以 docstrings 应该使用正确的参数名字。最好按照每行一个参数的形式列出来。举例,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
    ...

除非是所有内容都可以在一行内完全放下,否则把结尾的引号单独放在一行,这样 Emacs 的 fill-paragraph 命令就可以使用了。

处理 Docstrings 的缩进

docstrings 工具可以对 docstrings 的第二行及以后的行进行整体的缩进删除,删除的长度是后面这些行中的最小缩进,也就是说后面这些行的缩进最小化。第一行 docstrings 的任何缩进都是没有用的,会被删除。后续行的缩进也会被保留下来。应该删掉 docstrings 开头和结尾的空行。

因为代码比描述更准确,这里贴出来这个规则(算法)的实现,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def 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)

下面这个例子中的 docstrings 包含两个换行符号,所以一共有 3 行,第一行和最后一行是空行,

1
2
3
4
def foo():
    """
    This is the second line of the docstring.
    """

在命令行中运行一下看看,

1
2
3
4
5
6
>>> 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.'

一旦经过 trim 处理, 下面这两种 docstring 是等效的,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def foo():
    """A multi-line
    docstring.
    """

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

参考

参考阅读

PEP 256 -- Docstring Processing System Framework

PEP 258 -- Docutils Design Specification

附:实践

使用 flake8-docstrings 工具来帮助自己检查 docstrings 是否符合规范。

This post is part 4 of the "Python Notes" series:

  1. Python 学习笔记 #0 —— 新的开始
  2. Python 学习笔记 #1 —— PEP8 编程风格
  3. Python 学习笔记 #2 —— PEP8 实践
  4. Python 学习笔记 #3 —— Docstring 风格
  5. Python 学习笔记 #4 —— Python 之禅
  6. Python 学习笔记 #5 —— Comprehension 解析式
  7. Python 学习笔记 #6 —— Iterator 迭代器
  8. Python 学习笔记 #7 —— Generator 生成器
  9. Python 学习笔记 #8 —— Decorator 装饰器
  10. Python 学习笔记 #9 —— Function Arguments 函数参数
  11. Python 学习笔记 #10 —— Python 中的 FP