Python 文档字符串规范 | Google 官方 PEP-257 实战
注释和文档字符串 (docstring)
Tip
模块、函数、方法的文档字符串和内部注释一定要采用正确的风格。
文档字符串
Python 的文档字符串用于注释代码。文档字符串是包、模块、类或函数里作为第一个语句的字符串。可以用对象的 __doc__
成员自动提取这些字符串,并为 pydoc
所用。(可以试试在你的模块上运行 pydoc
并观察结果)。文档字符串一定要用三重双引号 """
的格式 (依据 PEP-257 )。文档字符串应该是一行概述 (整行不超过 80 个字符),以句号、问号或感叹号结尾。如果要写更多注释(推荐),那么概述后面必须紧接着一个空行,然后是剩下的内容,缩进与文档字符串的第一行第一个引号对齐。下面是更多有关文档字符串的格式规范。
模块
每个文件应该包含一个许可协议模版。应根据项目使用的许可协议(例如,Apache 2.0,BSD,LGPL,GPL)选择合适的模版。
文件的开头应该是文档字符串,其中应该描述该模块内容和用法。
"""模块或程序的一行概述,以句号结尾。
留一个空行。接下来应该写模块或程序的总体描述。也可以选择简要描述导出的类和函数,
和/或描述使用示例。
经典的使用示例:
foo = ClassFoo()
bar = foo.FunctionBar()
"""
测试模块
测试文件不必包含模块级文档字符串。只有在文档字符串可以提供额外信息时才需要写入文件。
例如,你可以描述运行测试时所需的特殊要求,解释不常见的初始化模式,描述外部环境的依赖等等。
"""这个 blaze 测试会使用样板文件(golden files)。
若要更新这些文件, 你可以在 `google3` 文件夹中运行
`blaze run //foo/bar:foo_test -- --update_golden_files`
"""
不要使用不能提供额外信息的文档字符串。
"""foo.bar 的测试."""
函数和方法
本节中的函数是指函数、方法、生成器(generator)和特性(property)。 满足下列任意特征的任何函数都必须有文档字符串:
- 公开 API 的一部分
- 长度过长
- 逻辑不能一目了然
文档字符串应该提供充分的信息,让调用者无需阅读函数的代码就能调用函数。文档字符串应该描述函数的调用语法和语义信息,而不应该描述具体的实现细节,除非这些细节会影响函数的用法。比如,如果函数的副作用是会修改某个传入的对象,那就需要在文档字符串中说明。对于微妙、重要但是与调用者无关的实现细节,相较于在文档字符串里说明,还是在代码中间加注释更好。
文档字符串可以是陈述句("""Fetches rows from a Bigtable."""
)或者祈使句 ("""Fetch rows from a Bigtable."""
),不过一个文件内的风格应当一致。对于 @property
修饰的数据描述符 (data descriptor),文档字符串应采用和属性 (attribute) 或函数参数 一样的风格 ("""Bigtable 路径."""
而非 """返回 Bigtable 路径."""
)。
对于覆写 (override) 基类 (base class) 方法的子类方法,可以用简单的文档字符串引导读者阅读基类方法的文档字符串,比如 """参见基类.""""
。这样是为了避免到处复制基类方法中已有的文档字符串。然而,如果覆写的子类方法与基类方法截然不同,或者有更多细节需要记录(例如有额外的的副作用),那么子类方法的文档字符串中至少要描述这些区别。
函数的部分特征应该在以下列出特殊小节中记录。每小节有一行标题,标题以冒号结尾。除标题行外,小节的其他部分应有2个或4个空格(同一文件内应保持一致)的悬挂缩进。如果函数名和函数签名(signature)可以见名知意,以至于一行文档字符串就能恰当地描述该函数,那么可以省略这些小节。
Args: (参数:)
列出所有参数名。参数名后面是一个冒号,然后是一个空格或者换行符,最后是描述。 如果描述过长以至于一行超出了 80 字符,则描述部分应该比参数名所在的行多2个或者4个空格(文件内应当一致)的悬挂缩进。如果代码没有类型注解,则描述中应该说明所需的类型。如果一个函数有形如 *foo
(可变长参数列表)或者 **bar
(任意关键字参数)的参数,那么列举参数名时应该写成 *foo
和 **bar
的这样的格式。
Returns: (“返回:”)
生成器应该用 “Yields:” (“生成:” )描述返回值的类型和意义。如果函数仅仅返回 None
,这一小节可以省略。如果文档字符串以 Returns (返回) 或者 Yields (生成) 开头 (例如 """返回 Bigtable 的行,类型是字符串构成的元组。"""
) 且这句话已经足以描述返回值,也可以省略这一小节。不要模仿 Numpy 风格的文档(例子)。他们在文档中记录作为返回值的元组时,写得就像返回值是多个值且每个值都有名字 (没有提到返回的是元组)。应该这样描述此类情况:“返回:一个元组 (mat_a, mat_b
),其中 mat_a
是…, 且 …
”。文档字符串中使用的辅助名称不需要和函数体的内部变量名一致 (因为这些名称不是 API 的一部分)。
Raises: (抛出:)
列出与接口相关的所有异常和异常描述。用类似 Args(参数)小节的格式,写成异常名+冒号+空格/换行,并添加悬挂缩进。不要在文档中记录违反 API 的使用条件时会抛出的异常(因为这会让违背 API 时出现的效果成为 API 的一部分,这是矛盾的)。
def fetch_smalltable_rows(
table_handle: smalltable.Table,
keys: Sequence[bytes | str],
require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
"""从 Smalltable 获取数据行.
从 table_handle 代表的 Table 实例中检索指定键值对应的行. 如果键值是字符串,
字符串将用 UTF-8 编码.
参数:
table_handle: 处于打开状态的 smalltable.Table 实例.
keys: 一个字符串序列, 代表要获取的行的键值. 字符串将用 UTF-8 编码.
require_all_keys: 如果为 True, 只返回那些所有键值都有对应数据的
行.
返回:
一个字典, 把键值映射到行数据上. 行数据是字符串构成的元组. 例如:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
返回的键值一定是字节串. 如果字典中没有 keys 参数中的某个键值, 说明
表格中没有找到这一行 (且 require_all_keys 一定是 false).
抛出:
IOError: 访问 smalltable 时出现错误.
"""
以下这种在 Args(参数)小节中换行的写法也是可以的:
def fetch_smalltable_rows(
table_handle: smalltable.Table,
keys: Sequence[bytes | str],
require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
"""从 Smalltable 获取数据行.
从 table_handle 代表的 Table 实例中检索指定键值对应的行. 如果键值是字符串,
字符串将用 UTF-8 编码.
参数:
table_handle:
处于打开状态的 smalltable.Table 实例.
keys:
一个字符串序列, 代表要获取的行的键值. 字符串将用 UTF-8 编码.
require_all_keys:
如果为 True, 只返回那些所有键值都有对应数据的行.
返回:
一个字典, 把键值映射到行数据上. 行数据是字符串构成的元组. 例如:
{b'Serak': ('Rigel VII', 'Preparer'),
b'Zim': ('Irk', 'Invader'),
b'Lrrr': ('Omicron Persei 8', 'Emperor')}
返回的键值一定是字节串. 如果字典中没有 keys 参数中的某个键值, 说明
表格中没有找到这一行 (且 require_all_keys 一定是 false).
抛出:
IOError: 访问 smalltable 时出现错误.
"""
类 (class)
类的定义下方应该有一个描述该类的文档字符串。如果你的类包含公有属性(attributes),应该在 Attributes
(属性)小节中记录这些属性,格式与函数的 Args
(参数)小节类似。
class SampleClass(object):
"""这里是类的概述.
这里是更多信息....
这里是更多信息....
属性:
likes_spam: 布尔值, 表示我们是否喜欢午餐肉.
eggs: 用整数记录的下蛋的数量.
"""
def __init__(self, likes_spam = False):
"""用某某某初始化 SampleClass."""
self.likes_spam = likes_spam
self.eggs = 0
def public_method(self):
"""执行某某操作."""
类的文档字符串开头应该是一行概述,描述类的实例所代表的事物。这意味着 Exception
的子类 (subclass) 应该描述这个异常代表什么,而不是描述抛出异常时的环境。类的文档字符串不应该有无意义的重复,例如说这个类是一种类。
正确:
class CheeseShopAddress:
"""奶酪店的地址.
...
"""
class OutOfCheeseError(Exception):
"""没有可用的奶酪."""
错误:
class CheeseShopAddress:
"""一个描述奶酪店地址的类.
...
"""
class OutOfCheeseError(Exception):
"""在没有可用的奶酪时抛出."""
块注释和行注释
最后一种需要写注释的地方是代码中复杂的部分。如果你可能在以后 代码评审 (code review) 时要解释某段代码,那么现在就应该给这段代码加上注释。应该在复杂的操作开始前写上若干行注释。对于不是一目了然的代码,应该在行尾添加注释。
## 我们用加权的字典搜索, 寻找 i 在数组中的位置. 我们基于数组中的最大值和数组
## 长度, 推断一个位置, 然后用二分搜索获得最终准确的结果.
if i & (i-1) == 0: # 如果 i 是 0 或者 2 的整数次幂, 则为真.
为了提高可读性,注释的井号和代码之间应有至少2个空格,井号和注释之间应该至少有一个空格。
除此之外,绝不要仅仅描述代码。应该假设读代码的人比你更懂Python,只是不知道你的代码要做什么。
## 不好的注释: 现在遍历数组 b, 确保每次 i 出现时, 下一个元素是 i+1
更多建议: