Google Python 代码注释与文档字符串风格完全指南
本文基于 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: {bSerak: (Rigel VII, Preparer), bZim: (Irk, Invader), bLrrr: (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 i1这种注释只是把代码翻译成了人话没有任何信息增量反而增加阅读负担。七、标点、拼写与语法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(fCannot write to home directory, $HOME{homedir!r})9.2 错误消息三原则异常信息、用户提示必须满足消息必须精确匹配实际错误情况插入的变量内容要清晰可识别便于自动化处理比如 grep 检索。✅ 正确python运行if not 0 p 1: raise ValueError(fNot 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后缀你也用如果注释有特殊格式你也保持一致。局部一致性的优先级甚至高于全局规范 —— 突兀的风格差异会打断读者的阅读节奏。
