本文基于 Google 官方 Python 风格指南,系统梳理 Python 代码中注释、文档字符串、TODO 的完整规范,包含正反示例与最佳实践,可作为团队代码规范与个人知识库归档。
一、为什么需要统一的注释风格
注释是代码可读性的核心保障。好的注释不描述代码本身在做什么,而是解释为什么这么做、业务意图、边界条件、潜在坑点。统一的注释风格能让团队成员快速读懂彼此的代码,降低维护成本。
Google Python 风格指南对注释的核心原则:
- 文档字符串(Docstring):对外说明「怎么用」,写给调用者看
- 行内注释(Inline Comment):对内说明「为什么」,写给维护者看
- 不做代码翻译:假设读者懂 Python,不需要解释基础语法
二、文档字符串(Docstrings)基础规范
2.1 基本格式
Python 使用三双引号"""作为文档字符串标准格式(遵循 PEP 257),禁止使用三单引号'''。
结构要求:
- 第一行是一句话摘要,不超过 80 字符,以句号、问号或感叹号结尾;
- 如果内容更多,空一行后续写详细说明;
- 详细内容与开头引号保持相同缩进位置。
2.2 正反示例
✅ 正确:
python
运行
def fetch_data(key: str) -> dict: """Fetches data from cache by key. Retrieves cached data for the given key. Returns an empty dict if the key does not exist. """❌ 错误:
python
运行
def fetch_data(key: str) -> dict: # 错误:使用单引号 '''Fetches data.''' def fetch_data(key: str) -> dict: """ 错误:首行就换行,没有摘要行 Fetches data from cache. """三、模块级文档字符串
3.1 规范要求
每个 Python 文件开头,在导入语句之前,必须有模块级文档字符串,描述文件的整体内容、用途、导出的类和函数、使用示例。
文件开头还应包含项目对应的许可证声明。
3.2 标准模板
python
运行
"""A one-line summary of the module or program, terminated by a period. Leave one blank line. The rest of this docstring should contain an overall description of the module or program. Optionally, it may also contain a brief description of exported classes and functions and/or usage examples. Typical usage example: foo = ClassFoo() bar = foo.function_bar() """3.3 测试模块的特殊说明
测试文件的模块文档字符串不是必需的。只有当存在额外信息时才添加,例如:
- 测试的特殊运行方式
- 不寻常的初始化模式
- 对外部环境的依赖
✅ 测试模块示例:
python
运行
"""This blaze test uses golden files. You can update those files by running `blaze run //foo/bar:foo_test -- --update_golden_files` from the `google3` directory. """❌ 无意义的文档字符串不要写:
python
运行
"""Tests for foo.bar.""" # 没有提供任何新信息,禁止使用四、函数与方法文档字符串
4.1 什么时候必须写文档字符串
满足以下任一条件的函数,强制要求文档字符串:
- 属于公开 API
- 函数体有一定规模
- 逻辑不直观、非显而易见
4.2 核心原则
文档字符串要让读者不看函数实现代码就能正确调用。描述调用语法和语义,不描述实现细节;但如果实现细节会影响使用(比如副作用),必须注明。
4.3 标准三段式结构
Args:参数说明
逐个列出参数名,冒号后接描述。如果代码没有类型注解,描述中必须包含类型。
*args和**kwargs也要列出。
Returns:返回值说明
描述返回值的语义和类型。如果函数只返回None,可以省略此节。
- 生成器函数用
Yields:而不是Returns:。
Raises:异常说明
列出所有与接口相关的异常及描述。不要列出因 API 误用而抛出的异常(比如参数校验错误),因为那不属于接口契约的一部分。
4.4 完整标准示例
python
运行
def fetch_smalltable_rows( table_handle: smalltable.Table, keys: Sequence[bytes | str], require_all_keys: bool = False, ) -> Mapping[bytes, tuple[str, ...]]: """Fetches rows from a Smalltable. Retrieves rows pertaining to the given keys from the Table instance represented by table_handle. String keys will be UTF-8 encoded. Args: table_handle: An open smalltable.Table instance. keys: A sequence of strings representing the key of each table row to fetch. String keys will be UTF-8 encoded. require_all_keys: If True only rows with values set for all keys will be returned. Returns: A dict mapping keys to the corresponding table row data fetched. Each row is represented as a tuple of strings. For example: {b'Serak': ('Rigel VII', 'Preparer'), b'Zim': ('Irk', 'Invader'), b'Lrrr': ('Omicron Persei 8', 'Emperor')} Returned keys are always bytes. If a key from the keys argument is missing from the dictionary, then that row was not found in the table (and require_all_keys must have been False). Raises: IOError: An error occurred accessing the smalltable. """4.5 重写方法的特殊规则
子类重写父类方法时:
- 如果加了
@override装饰器,且重写没有改变语义,可以不写文档字符串; - 如果重写后语义有变化、增加了副作用,必须写文档字符串说明差异;
- 写
"""See base class."""也是允许的,但@override本身就足够说明文档在父类。
✅ 正确示例:
python
运行
from typing_extensions import override class Child(Parent): @override def do_something(self): pass # 有 @override,可以不写文档字符串五、类文档字符串
5.1 规范要求
类定义下方必须有文档字符串,描述类的用途。公开属性(不包括 property)需要在Attributes:节中列出,格式与函数的Args:一致。
5.2 标准示例
python
运行
class SampleClass: """Summary of class here. Longer class information... Longer class information... Attributes: likes_spam: A boolean indicating if we like SPAM or not. eggs: An integer count of the eggs we have laid. """ def __init__(self, likes_spam: bool = False): """Initializes the instance based on spam preference. Args: likes_spam: Defines if instance exhibits this preference. """ self.likes_spam = likes_spam self.eggs = 05.3 异常类命名与文档
异常类的文档字符串应该描述异常本身代表什么,而不是在什么上下文抛出。
✅ 正确:
python
运行
class OutOfCheeseError(Exception): """No more cheese is available."""❌ 错误:
python
运行
class OutOfCheeseError(Exception): """Raised when no more cheese is available.""" # 冗余表述六、块注释与行内注释
6.1 使用场景
- 复杂操作:在操作前写几行块注释说明整体思路;
- 不直观的技巧:在行尾加行内注释解释;
- 代码评审时需要解释的逻辑,现在就写注释。
6.2 格式要求
- 行内注释与代码之间至少间隔 2 个空格;
#号后至少跟 1 个空格再写注释文字;- 绝对不要描述代码本身在做什么,假设读者懂 Python。
✅ 正确示例:
python
运行
# We use a weighted dictionary search to find out where i is in # the array. We extrapolate position based on the largest num # in the array and the array size and then do binary search to # get the exact number. if i & (i - 1) == 0: # True if i is 0 or a power of 2. process(i)❌ 反面典型(废话注释):
python
运行
# BAD: Now go through the b array and make sure whenever i occurs # the next element is i+1这种注释只是把代码翻译成了人话,没有任何信息增量,反而增加阅读负担。
七、标点、拼写与语法
7.1 基本要求
- 注释要像叙述文本一样可读,正确使用大小写和标点;
- 完整句子比句子片段更易读;
- 行尾短注释可以非正式,但全篇风格要保持一致;
- 注意拼写和语法,避免低级错误。
7.2 为什么如此重要
虽然标点和拼写看似小事,但源代码的清晰度和可读性对长期维护至关重要。规范的注释能显著降低团队沟通成本和新人上手成本。
八、TODO 注释规范
8.1 什么时候用 TODO
用于临时方案、短期解决方案、「够用但不完美」的代码。
8.2 标准格式
plaintext
# TODO: 引用链接 - 说明文字TODO全大写,后面跟冒号;- 冒号后是上下文引用(最好是 bug 链接,而不是人名);
- 然后用连字符
-引出说明文字。
✅ 正确:
python
运行
# TODO: crbug.com/192795 - Investigate cpufreq optimizations.❌ 不推荐的旧写法:
python
运行
# TODO(crbug.com/192795): Investigate cpufreq optimizations. # TODO(yourusername): Use a "*" here for concatenation operator.8.3 禁止事项
- 不要用个人用户名作为 TODO 的上下文;
- TODO 要可追溯、可跟进,指向 issue 或 bug 最佳;
- 如果写「将来某天做某事」,务必给出具体日期或具体触发事件。
九、日志与错误消息中的文字规范
9.1 日志字符串规范
日志函数第一个参数用字符串字面量,不要用 f-string。原因:
- 部分日志实现会将原始模式字符串作为可查询字段;
- 避免为未启用的日志级别浪费渲染时间。
✅ 正确:
python
运行
logging.info('Current $PAGER is: %s', os.getenv('PAGER', default=''))❌ 错误:
python
运行
logging.info(f'Cannot write to home directory, $HOME={homedir!r}')9.2 错误消息三原则
异常信息、用户提示必须满足:
- 消息必须精确匹配实际错误情况;
- 插入的变量内容要清晰可识别;
- 便于自动化处理(比如 grep 检索)。
✅ 正确:
python
运行
if not 0 <= p <= 1: raise ValueError(f'Not a probability: {p=}')❌ 反面示例:
python
运行
try: os.rmdir(workdir) except OSError: # 问题:想当然认为是目录已删除,实际可能因为其他原因失败 logging.warning('Directory already was deleted: %s', workdir)十、核心原则总结
10.1 文档字符串 vs 行内注释
表格
| 类型 | 受众 | 内容重点 | 位置 |
|---|---|---|---|
| 文档字符串 | 调用者 / 使用者 | 怎么用、入参出参、异常 | 函数 / 类 / 模块开头 |
| 行内注释 | 维护者 / 阅读者 | 为什么这么做、坑点、技巧 | 代码旁 / 行尾 |
10.2 五条黄金法则
- 不翻译代码:不说「这行在循环」,说「为什么要循环、循环解决什么问题」;
- 公开 API 必有文档字符串:别人不看你源码就能用;
- 参数、返回值、异常三段式:结构统一,快速检索;
- TODO 可追溯:挂 issue 链接,不写人名;
- 保持一致性:局部风格比全局规范更重要,与周边代码风格统一。
10.3 最后的话
BE CONSISTENT. 风格指南的意义是让大家拥有共同的编码词汇,从而专注于表达内容,而非表达方式。
如果你修改已有代码,先花几分钟观察周边代码的风格。如果周围用_idx后缀,你也用;如果注释有特殊格式,你也保持一致。局部一致性的优先级甚至高于全局规范 —— 突兀的风格差异会打断读者的阅读节奏。