Google Python Style in Chinese (谷歌 Python 代码规范)
Source: https://google.github.io/styleguide/pyguide.html
目录
- 背景
- Python 语言规则 2.1 代码检查工具(Lint) 2.2 导入语句(Imports) 2.3 包(Packages) 2.4 异常处理(Exceptions) 2.5 可变全局状态(Mutable Global State) 2.6 嵌套/局部/内部类和函数(Nested/Local/Inner Classes and Functions) 2.7 推导式与生成器表达式(Comprehensions & Generator Expressions) 2.8 默认迭代器和操作符(Default Iterators and Operators) 2.9 生成器(Generators) 2.10 Lambda 函数(Lambda Functions) 2.11 条件表达式(Conditional Expressions) 2.12 默认参数值(Default Argument Values) 2.13 属性(Properties) 2.14 布尔值判断(True/False Evaluations) 2.16 词法作用域(Lexical Scoping) 2.17 函数和方法装饰器(Function and Method Decorators) 2.18 多线程(Threading) 2.19 高级特性(Power Features) 2.20 现代 Python:未来导入(Modern Python: from future imports) 2.21 类型注解代码(Type Annotated Code)
- Python 风格规则 3.1 分号(Semicolons) 3.2 行长度(Line length) 3.3 括号(Parentheses) 3.4 缩进(Indentation) 3.4.1 序列中项的末尾逗号(Trailing commas in sequences of items?) 3.5 空行(Blank Lines) 3.6 空格(Whitespace) 3.7 Shebang 行(Shebang Line) 3.8 注释和文档字符串(Comments and Docstrings) 3.8.1 文档字符串(Docstrings) 3.8.2 模块(Modules) 3.8.2.1 测试模块(Test modules) 3.8.3 函数和方法(Functions and Methods) 3.8.3.1 重写方法(Overridden Methods) 3.8.4 类(Classes) 3.8.5 块注释和行内注释(Block and Inline Comments) 3.8.6 标点、拼写和语法(Punctuation, Spelling, and Grammar) 3.10 字符串(Strings) 3.10.1 日志记录(Logging) 3.10.2 错误消息(Error Messages) 3.11 文件、套接字和类似有状态资源(Files, Sockets, and similar Stateful Resources) 3.12 TODO 注释(TODO Comments) 3.13 导入格式(Imports formatting) 3.14 语句(Statements) 3.15 访问器(Accessors) 3.16 命名(Naming) 3.16.1 应避免的名称(Names to Avoid) 3.16.2 命名约定(Naming Conventions) 3.16.3 文件命名(File Naming) 3.16.4 源自 Guido 建议的指南(Guidelines derived from Guido’s Recommendations) 3.17 主函数(Main) 3.18 函数长度(Function length) 3.19 类型注解(Type Annotations) 3.19.1 通用规则(General Rules) 3.19.2 换行(Line Breaking) 3.19.3 前向声明(Forward Declarations) 3.19.4 默认值(Default Values) 3.19.5 NoneType(NoneType) 3.19.6 类型别名(Type Aliases) 3.19.7 忽略类型(Ignoring Types) 3.19.8 类型变量(Typing Variables) 3.19.9 元组 vs 列表(Tuples vs Lists) 3.19.10 类型变量(Type variables) 3.19.11 字符串类型(String types) 3.19.12 类型导入(Imports For Typing) 3.19.13 条件导入(Conditional Imports) 3.19.14 循环依赖(Circular Dependencies) 3.19.15 泛型(Generics) 3.19.16 构建依赖(Build Dependencies)
- 结语(Parting Words)
Python 是 Google 使用的主要动态语言。本风格指南列出了 Python 程序的注意事项。
为帮助您正确格式化代码,我们为 Vim 创建了一个设置文件。对于 Emacs,默认设置应该就可以。
许多团队使用 Black 或 Pyink 自动格式化工具来避免因格式问题产生争议。
使用此 pylintrc 对代码运行 pylint
。
pylint
是一个用于查找 Python 源代码中错误和风格问题的工具。它能发现像拼写错误、变量未赋值前使用等容易被忽略的问题,这些问题在 C 和 C++ 等动态性较低的语言中通常会被编译器捕获。由于 Python 的动态特性,某些警告可能不准确,但误报应该很少见。
捕捉容易遗漏的错误,如拼写错误、变量未赋值前使用等。
pylint
并非完美。为了利用它,有时我们需要绕过它、抑制其警告或修复它。
确保对代码运行 pylint
。
如果警告不恰当,请抑制它们,以免掩盖其他问题。要抑制警告,可以设置行级注释:
def do_PUT(self): # WSGI 名称,因此 pylint: disable=invalid-name
...
pylint
警告每个都有符号名称(如 empty-docstring
),Google 特定的警告以 g-
开头。
如果从符号名称中无法清楚看出抑制原因,请添加解释。
以这种方式抑制警告的优点是我们可以轻松搜索抑制项并重新审视它们。
您可以通过以下方式获取 pylint
警告列表:
pylint --list-msgs
要获取特定消息的更多信息,请使用:
pylint --help-msg=invalid-name
prefer pylint: disable
而不是已弃用的旧形式 pylint: disable-msg
。
未使用的参数警告可以通过在函数开头删除变量来抑制。始终包含一条注释解释删除原因,“未使用”即可。例如:
def viking_cafe_order(spam: str, beans: str, eggs: str | None = None) -> str:
del beans, eggs # 维京人未使用。
return spam + spam + spam
抑制此警告的其他常见形式包括将未使用的参数标识符设为 _
或在参数名称前加 unused_
,或将它们赋值给 _
。这些形式是允许的,但不再鼓励。因为这会破坏通过名称传递参数的调用者,并且无法强制参数实际未被使用。
仅对包和模块使用 import
语句,不要对单个类型、类或函数使用。
是将代码从一个模块共享到另一个模块的可重用机制。
命名空间管理约定简单。每个标识符的来源以一致的方式显示;x.Obj
表示 Obj
对象在模块 x
中定义。
模块名称仍可能冲突,有些模块名称过长不便使用。
使用 import x
导入包和模块。
使用 from x import y
,其中 x
是包前缀,y
是不带前缀的模块名。
在以下任一情况下使用 from x import y as z
:
- 要导入两个同名模块
y
。 y
与当前模块中定义的顶级名称冲突。y
与作为公共 API 一部分的常用参数名称冲突(如features
)。y
是一个过长的名称。y
在代码上下文中过于通用(例如from storage.file_system import options as fs_options
)。
仅当z
是标准缩写时(例如import numpy as np
),使用import y as z
。
例如,模块sound.effects.echo
可以按如下方式导入:
from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)
不要在导入中使用相对名称。即使模块在同一个包中,也要使用完整的包名。这有助于防止意外重复导入包。
以下模块的符号可例外,用于支持静态分析和类型检查:
typing
模块collections.abc
模块typing_extensions
模块- 来自 six.moves 模块的重定向。
使用模块的完整路径名导入每个模块。
避免因模块搜索路径不符合作者预期而导致的模块名称冲突或错误导入,使模块更易查找。
使代码部署更困难,因为必须复制包层次结构。但对于现代部署机制来说,这并不是真正的问题。
所有新代码都应使用完整的包名导入每个模块。
导入应如下所示:
# 正确:在代码中使用完整名称引用 absl.flags(详细)。
import absl.flags
from doctor.who import jodie
_FOO = absl.flags.DEFINE_string(...)
# 正确:在代码中仅使用模块名引用 flags(常见)。
from absl import flags
from doctor.who import jodie
_FOO = flags.DEFINE_string(...)
(假设此文件位于 doctor/who/
目录中,其中也存在 jodie.py
)
# 错误:不清楚作者想导入哪个模块以及实际会导入什么。实际导入行为取决于控制 sys.path 的外部因素。
# 作者打算导入哪个可能的 jodie 模块?
import jodie
不应假设主二进制文件所在的目录在 sys.path
中,尽管在某些环境中可能如此。因此,代码应假设 import jodie
指的是名为 jodie
的第三方或顶级包,而不是本地的 jodie.py
。
允许使用异常,但必须谨慎使用。
异常是一种跳出正常控制流以处理错误或其他异常情况的手段。
正常操作代码的控制流不会被错误处理代码干扰,还允许在特定条件发生时跳过多个框架,例如一步从 N 个嵌套函数中返回,而不必通过错误代码逐层传递。
可能导致控制流混乱,在进行库调用时容易遗漏错误情况。
异常必须遵循以下条件:
在合理的情况下使用内置异常类。例如,引发 ValueError
以指示编程错误,如违反前置条件,这可能在验证函数参数时发生。
不要使用 assert
语句代替条件判断或验证前置条件。它们不能是应用逻辑的关键部分。一个试金石测试是,assert
可以在不破坏代码的情况下被移除。assert
条件不一定会被评估。对于基于 pytest 的测试,assert
是可以的,并且用于验证预期。例如:
# 正确:
def connect_to_next_port(self, minimum: int) -> int:
"""连接到下一个可用端口。
参数:
minimum:大于或等于 1024 的端口值。
返回:
新的最小端口。
引发:
ConnectionError:如果未找到可用端口。
"""
if minimum < 1024:
# 注意,此 ValueError 的引发未在文档字符串的“Raises:”部分提及,因为不适合保证对 API 误用的具体行为反应。
raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
port = self._find_next_open_port(minimum)
if port is None:
raise ConnectionError(
f'Could not connect to service on port {minimum} or higher.')
# 代码不依赖此 assert 的结果。
assert port >= minimum, (
f'Unexpected port {port} when minimum was {minimum}.')
return port
# 错误:
def connect_to_next_port(self, minimum: int) -> int:
"""连接到下一个可用端口。
参数:
minimum:大于或等于 1024 的端口值。
返回:
新的最小端口。
"""
assert minimum >= 1024, 'Minimum port must be at least 1024.'
# 以下代码依赖于前面的 assert。
port = self._find_next_open_port(minimum)
assert port is not None
# 返回语句的类型检查依赖于 assert。
return port
库或包可以定义自己的异常。此时,它们必须继承自现有异常类。异常名称应以 Error
结尾,且不应重复(如 foo.FooError
)。
除非您要重新引发异常,或在程序中创建一个隔离点,在此处异常不传播,而是被记录和抑制(例如通过保护线程的最外层块防止线程崩溃),否则切勿使用通配符 except:
语句,或捕获 Exception
或 StandardError
。
Python 在这方面非常宽容,except:
实际上会捕获所有异常,包括拼写错误的名称、sys.exit()
调用、Ctrl+C
中断、单元测试失败以及各种其他您根本不想捕获的异常。
尽量减少 try
/except
块中的代码量。try
的主体越大,就越有可能有您不期望引发异常的代码行引发异常。在这些情况下,try
/except
块会隐藏真正的错误。
使用 finally
子句来执行代码,无论 try
块中是否引发异常。这通常用于清理资源,例如关闭文件。
避免可变全局状态。
在程序执行期间可能被修改的模块级值或类属性。
偶尔有用。
破坏封装性:这种设计可能使实现有效目标变得困难。例如,如果使用全局状态管理数据库连接,那么同时连接到两个不同的数据库(如在迁移期间计算差异时)就会变得困难。类似的问题在全局注册表中也容易出现。
由于对全局变量的赋值在模块首次导入时完成,因此可能在导入期间改变模块行为。
避免可变全局状态。
在极少数需要使用全局状态的情况下,可变全局实体应在模块级别声明或作为类属性,并通过在名称前加 _
使其成为内部成员。如有必要,必须通过公共函数或类方法访问可变全局状态。请参阅下面的命名规则。请在注释或与注释链接的文档中解释使用可变全局状态的设计原因。
允许并鼓励使用模块级常量。例如:_MAX_HOLY_HANDGRENADE_COUNT = 3
(内部使用的常量)或 SIR_LANCELOTS_FAVORITE_COLOR = "blue"
(公共 API 常量)。常量必须使用全大写字母加下划线命名。请参阅下面的命名规则。
当用于闭包一个局部变量时,嵌套的局部函数或类是可以的。内部类是可以的。
类可以在方法、函数或类内部定义。函数可以在方法或函数内部定义。嵌套函数对封闭作用域中定义的变量具有只读访问权。
允许定义仅在非常有限的作用域内使用的实用类和函数,非常适合抽象数据类型(ADT)。常用于实现装饰器。
嵌套函数和类无法直接测试。嵌套可能使外部函数更长且更难阅读。
它们在某些情况下是可以的。除非闭包一个除 self
或 `cls