谷歌 Python 代码规范
styleguide | Style guides for Google-originated open-source projects
可读性 + 简洁性
1 背景
Python 是 Google 使用的主要动态语言。本风格指南列出了 Python 程序的注意事项。
为了帮助您正确格式化代码,我们为 Vim 创建了一个设置文件。对于 Emacs,默认设置应该没问题。
许多团队使用 Black 或 Pyink 自动格式化程序来避免格式争论。
2 Python 语言规则
2.1 Lint(代码检测)
使用此 pylintrc 在您的代码上运行 pylint。
...
2.2 Imports 导入
仅对包和模块(packages and modules)使用 import
语句,而不是对单个类型、类或函数使用 import
语句。
2.2.1 定义
将代码从一个模块共享到另一个模块的可重用性机制。
2.2.2 优点
命名空间管理约定很简单。每个标识符的来源以一致的方式指示; x.Obj
表示对象 Obj
是在模块 x
中定义的。
2.2.3 缺点
模块名称仍然可能发生冲突(collide)。有些模块名称太长,不方便。
2.2.4 决定
- 使用
import x
导入包和模块。 - 使用
from x import y
,其中x
是包前缀,y
是不带前缀的模块名称。 - 使用
from x import y as z
在以下任一情况下:- 两个模块
y
- 使用
import y as z
仅当z
是一个是标准缩写(abbreviation)时,例如import numpy as np
例如,模块 sound.effects.echo
可以按如下方式导入:
from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)
不要在导入中使用相对名称。即使模块位于同一个包中,也要使用完整的包名称。这有助于防止无意中两次导入包。
2.3 packages 包
使用模块的完整路径名位置导入每个模块。
2.3.1 优点
避免模块名称冲突或由于模块搜索路径与作者预期不符而导致的错误导入。使查找模块变得更加容易。
2.3.2 缺点
使部署代码变得更加困难,因为您必须复制包层次结构。对于现代部署机制来说,这并不是真正的问题。
2.3.3 决定
所有新代码都应通过完整的包名称导入每个模块。
导入应为如下:
# Reference absl.flags in code with the complete name (verbose).
import absl.flags
from doctor.who import jodie
_FOO = absl.flags.DEFINE_string(...)
尽管在某些环境中发生这种情况,但不应假定主二进制文件所在的目录位于 sys.path 中。既然如此,代码应该假设 import jodie 引用的是名为 jodie 的第三方或顶级包,而不是本地 jodie.py。
2.4 Exceptions 异常
2.5 Mutable Global State 可变的全局状态
2.5.1 定义
2.5.2 优点
2.5.3 缺点
2.6 Nested/Local/Inner Classes and Functions 嵌套/本地/内部类和函数
2.6.1 定义
2.6.2 优点
2.7 Comprehensions & Generator Expressions 推导式和生成器表达式
可以用于简单的情况。
2.7.1 定义
列表、字典和集合推导式以及生成器表达式提供了一种简洁有效的方法来创建容器类型和迭代器,而无需使用传统的循环、map()
、filter()
或 lambda
。
2.7.2 优点
简单推导式比其他字典、列表或集合创建技术更清晰、更简单。生成器表达式非常高效,因为它们完全避免了列表的创建。
2.7.3 缺点
复杂的推导式或生成器表达式可能难以阅读。
2.7.4 决定
允许使用推导式,但不允许使用多个 for
子句或过滤表达式。优化可读性,而不是简洁性。
# Yes:
result = [mapping_expr for value in iterable if filter_expr]
result = [
is_valid(metric={'key': value})
for value in interesting_iterable
if a_longer_filter_expression(value)
]
descriptive_name = [
transform({'key': key, 'value': value}, color='black')
for key, value in generate_iterable(some_input)
if complicated_condition_is_met(key, value)
]
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
return {
x: complicated_transform(x)
for x in long_generator_function(parameter)
if x is not None
}
return (x**2 for x in range(10))
unique_names = {user.name for user in users if user is not None}
错误示范:
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
return (
(x, y, z)
for x in range(5)
for y in range(5)
if x != y
for z in range(5)
if y != z
)
2.8 Default Iterators and Operators 默认迭代器和运算符
对支持它们的类型(例如列表、字典和文件)使用默认迭代器和运算符。
2.8.1 定义
容器类型(如字典和列表)定义默认迭代器和成员测试运算符(“in”和“not in”)。
2.8.2 优点
默认的迭代器和运算符简单而高效。它们直接表达操作,无需额外的方法调用。使用默认运算符的函数是通用的。它可以与支持该操作的任何类型一起使用。
2.8.3 缺点
您无法通过读取方法名称来判断对象的类型(除非变量具有类型注释)。这也是一个优点。
2.8.4 决定
对支持它们的类型(例如列表、字典和文件)使用默认迭代器和运算符。内置类型也定义了迭代器方法。优先选择这些方法而不是返回列表的方法,除非您在迭代容器时不应该改变容器。
# Yes:
for key in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
错误示范:
# No:
for key in adict.keys(): ...
for line in afile.readlines(): ...
2.9 Generators 生成器
2.10 Lambda Functions 匿名函数
2.12 Default Argument Values 默认参数值
大多数情况下都可以。
2.12.1 定义
您可以在函数参数列表末尾指定变量值,例如 def foo(a, b=0)
:。如果调用 foo
时仅给出了一个参数,则 b
设置为 0
。如果使用两个参数调用 foo
,则 b
具有第二个参数的值。
2.12.4 决定
可以使用,但有以下警告:
不要在函数或方法定义中使用 可变对象作为默认值。
# Yes:
def foo(a, b=None):
if b is None:
b = []
def foo(a, b: Sequence | None = None):
if b is None:
b = []
def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable.
2.13 属性
3 Python 风格规则
3.1 Semicolons 分号
不要用分号终止行,也不要使用分号将两个语句放在同一行。
3.2 Line length 行长
最大行长度为 80 个字符。
80 个字符限制的明确例外:
- 长导入语句。
- 注释中的 URL、路径名或长标志。
- 不包含空格的长字符串模块级常量,不方便跨行分割,例如 URL 或路径名。
- Pylint 禁用评论。 (例如:# pylint:disable=invalid-name)
3.3 Parentheses 括号
3.4 Indentation 缩进
将代码块缩进 4 个空格。
切勿使用 Tab 键。隐含的行延续应垂直对齐换行元素(请参阅行长度示例),或使用悬挂的 4 个空格缩进。右括号(圆括号、方括号或大括号)可以放置在表达式的末尾或单独的行上,但应与相应左括号所在的行缩进相同。
# Yes:
# Aligned with opening delimiter
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)
# Aligned with opening delimiter in a dictionary
foo = {
'long_dictionary_key': value1 +
value2,
...
}
# 4-space hanging indent; nothing on first line.
foo = long_function_name(
var_one, var_two, var_three,
var_four)
meal = (
spam,
beans)
# 4-space hanging indent; nothing on first line,
# closing parenthesis on a new line.
foo = long_function_name(
var_one, var_two, var_three,
var_four
)
meal = (
spam,
beans,
)
# 4-space hanging indent in a dictionary.
foo = {
'long_dictionary_key':
long_dictionary_value,
...
}
3.4.1 项目序列中的尾随逗号?
仅当结束容器标记 ]
、)
或 }
不与最终元素出现在同一行 时,以及对于 具有单个元素的元组 时,才建议在项目序列中使用尾随逗号。尾随逗号的存在也用作对 Python 代码自动格式化程序 Black 或 Pyink 的提示,指示它在最后一个元素后面出现 ,
时将项目容器自动格式化为每行一项。
# Yes:
golomb3 = [0, 1, 3]
golomb4 = [
0,
1,
4,
6,
]
错误示范:
# No:
golomb4 = [
0,
1,
4,
6,]
3.5 Blank Lines 空行
顶级定义(top-level definitions)之间有两个空行,无论是函数定义还是类定义。方法定义之间以及类的文档字符串(docstring)和第一个方法之间有一个空行。 def
行后面没有空行。在函数或方法中使用您认为合适的单个空行。
空行不需要锚定到定义中。例如,紧邻函数、类和方法定义之前的相关注释可能是有意义的。考虑一下您的评论作为文档字符串的一部分是否会更有用。
3.6 Whitespace 空白
遵循标准印刷规则来使用标点符号周围的空格。
圆括号、方括号或大括号内不能有空格。
# Yes:
spam(ham[1], {'eggs': 2}, [])
错误示范:
# No:
spam( ham[ 1 ], { 'eggs': 2 }, [ ] )
逗号、分号或冒号前不能有空格。请在逗号、分号或冒号后使用空格,行尾除外。
# Yes:
if x == 4:
print(x, y)
x, y = y, x
错误示范:
# No:
if x == 4 :
print(x , y)
x , y = y , x
开始参数列表、索引或切片的左括号/括号之前没有空格。
# Yes:
spam(1)
dict['key'] = list[index]
错误示范:
# No:
spam (1)
dict ['key'] = list [index]
没有尾随空格。
二元运算符两边各有一个空格,用于赋值 (=)、比较(==、<、>、!=、<>、<=、>=、in、not in、is、is not)和布尔值(和、或、不是)。使用您更好的判断来在算术运算符(+、-、*、/、//、%、**、@)周围插入空格。
3.7 Shebang Line 舍邦线
舍邦线最早用于告诉 Linux 使用哪个 Shell 解释器。例如 #!/bin/bash
。
大多数 .py 文件不需要以 # 开头!线。根据 PEP-394,使用 #!/usr/bin/env python3
(以支持 virtualenvs
)或 #!/usr/bin/python3
启动程序的主文件。
该行被内核用来查找 Python 解释器,但在导入模块时被 Python 忽略。仅对于要直接执行的文件才需要。
3.8 Comments and Docstrings 评论和文档字符串
3.8.2 模块
每个文件都应包含许可证样板。为项目使用的许可证选择适当的样板(例如,Apache 2.0、BSD、LGPL、GPL)。
文件应以描述模块内容和用法的文档字符串开头。
"""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.FunctionBar()
"""
3.8.2.1 测试模块
不需要测试文件的模块级文档字符串。仅当可以提供附加信息时才应包含它们。
示例包括有关如何运行测试的一些细节、对不寻常设置模式的解释、对外部环境的依赖等。
"""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.
"""
3.8.3 函数和方法
在本节中,“函数”是指方法、函数、生成器或属性。
对于具有以下一个或多个属性的每个函数,文档字符串都是必需的:
- 是公共 API 的一部分
- 不平凡的规模
- 不明显的逻辑
文档字符串应该提供足够的信息来编写对函数的调用,而无需阅读函数的代码。文档字符串应该描述函数的调用语法及其语义,但通常 不描述其实现细节,除非这些细节与函数的使用方式相关。例如,作为副作用而 改变其参数之一的函数 应在其文档字符串中注明这一点。否则,与调用者无关的函数实现的微妙但重要的细节最好在 代码旁边表达为注释,而不是在函数的文档字符串中表达。
文档字符串可以是描述性风格("""Fetches rows from a Bigtable."""
)或命令式("""Fetch rows from a Bigtable."""
),但风格在文件内应该一致。 @property
数据描述符的文档字符串应使用与属性或函数参数的文档字符串相同的样式("""The Bigtable path."""
,而不是 """Returns the Bigtable path."""
)。
函数的某些方面应记录在下面列出的特殊部分中。每个部分都以标题行开始,以冒号结束。除标题之外的所有部分都应保持两个或四个空格的悬挂缩进(在文件内保持一致)。如果函数的名称和签名信息足够丰富,可以使用一行文档字符串来恰当地描述,则可以省略这些部分。
3.8.3.1 参数 Args
按名称列出每个参数。描述应该跟在名称后面,并用冒号分隔,后跟空格或换行符。如果描述太长,无法容纳在单个 80 个字符的行中,请使用比参数名称多 2 或 4 个空格的悬挂缩进(与文件中的其余文档字符串一致)。如果代码不包含相应的类型注释,则描述应包括所需的类型。如果函数接受 *foo
(可变长度参数列表)和/或 **bar
(任意关键字参数),则它们应列为 *foo
和 **bar
。
3.8.3.2 返回值 Returns
或对生成器的抛出值 Yields: for generators。
描述返回值的语义,包括类型注释未提供的任何类型信息。如果函数仅返回 None
,则不需要此部分。如果文档字符串以 Returns
或 Yields
开头(例如 """Returns row from Bigtable as a tuple of strings."""
)并且开头句足以描述返回值,则也可以省略它。不要模仿旧的“NumPy 风格”(示例),它经常记录一个元组返回值,就好像它是具有单独名称的多个返回值(从不提及元组)。相反,将这样的返回值描述为:“返回:一个元组 (mat_a, mat_b),其中 mat_a 是 ..., 和 ...”。文档字符串中的辅助名称不一定与函数体中使用的任何内部名称相对应(因为它们不是 API 的一部分)。
3.8.3.3 抛出 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, ...]]:
"""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.
"""
同样,Args:
的这种变体也允许换行:
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.
"""
3.8.3.4 重写方法 Overridden Methods
如果从基类重写方法的方法显式地用 @override
修饰(来自 typing_extensions
或类型模块),则不需要文档字符串,除非重写方法的行为实质上细化了基方法的契约,或者需要提供详细信息(例如,记录额外的副作用),在这种情况下,重写方法需要至少具有这些差异的文档字符串。
from typing_extensions import override
class Parent:
def do_something(self):
"""Parent method, includes docstring."""
# Child class, method annotated with override.
class Child(Parent):
@override
def do_something(self):
pass
4 Parting Words 临别赠言
始终如一 BE CONSISTENT。
如果您正在编辑代码,请花几分钟查看周围的代码并确定其风格。如果他们在索引变量名称中使用 _idx 后缀,您也应该这样做。如果他们的评论周围有小方框的哈希标记,那么您的评论周围也有小方框的哈希标记。
制定风格指南的目的是拥有通用的编码词汇,这样人们就可以专注于你所说的内容,而不是你所说的方式。我们在这里介绍全局样式规则,以便人们了解词汇,但本地样式也很重要。如果您添加到文件中的代码看起来与它周围的现有代码截然不同,那么读者在阅读该文件时就会失去节奏。
然而,一致性是有限制的。它更适用于本地和全局风格未指定的选择。一般来说,一致性不应该被用作以旧风格做事的理由,而不考虑新风格的好处,或者代码库随着时间的推移向新风格收敛的趋势。