代码检查
一定要用pylint检查你的代码.
import
- 用
import x
来导入包和模块. - 用
from x import y
, 其中x是包前缀, y是不带前缀的模块名. - 在以下情况使用
from x import y as z
: 如果有两个模块都叫y
; 如果y
和当前模块的某个全局名称冲突; 如果y
是长度过长的名称. - 仅当缩写
z
是标准缩写时才能使用import y as z
.(比如np
代表numpy
.)
所有新的代码都应该用完整包名来导入每个模块.
异常
使用异常时必须遵守特定要求:
-
优先使用合适的内置异常类. 比如, 用
ValueError
表示前置条件错误 (例如给必须为正数的参数传入了负值). 不要使用assert
语句来验证公开API的参数值. 应该用assert
来保证内部正确性, 不应该用assert
来纠正参数或表示意外情况. 若要用异常来表示意外情况, 应该用raise
. 例如:正确:
def connect_to_next_port(self, minimum: int) -> int: """连接到下一个可用的端口. Args: minimum: 一个大于等于 1024 的端口号. Returns: 新的最小端口. Raises: ConnectionError: 没有可用的端口. """ if minimum < 1024: # 注意这里抛出 ValueError 的情况没有在文档里说明,因为 API 的 # 错误用法应该是未定义行为. raise ValueError(f'最小端口号至少为 1024,不能是 {minimum}.') port = self._find_next_open_port(minimum) if port is None: raise ConnectionError( f'未能通过 {minimum} 或更高的端口号连接到服务.') assert port >= minimum, ( f'意外的端口号 {port}, 端口号不应小于 {minimum}.') return port
错误:
def connect_to_next_port(self, minimum: int) -> int: """连接到下一个可用的端口. Args: minimum: 一个大于等于 1024 的端口号. Returns: 新的最小端口. """ assert minimum >= 1024, '最小端口号至少为 1024.' port = self._find_next_open_port(minimum) assert port is not None return port
-
模块或包可以定义自己的异常类型, 这些类必须继承已有的异常类. 异常类型名应该以
Error
为后缀, 并且不应该有重复 (例如foo.FooError
). -
永远不要使用
except:
语句来捕获所有异常, 也不要捕获Exception
或者StandardError
, 除非你想:- 重新抛出异常.
- 在程序中创造一个隔离点, 记录并抑制异常, 让异常不再继续传播. 这种写法可以用在线程的最外层, 以避免程序崩溃.
如果你使用这种写法, Python 将非常宽容.
except:
真的会捕获任何错误, 包括拼写错误的符号名、sys.exit()
调用、Ctrl+C
中断、单元测试错误和各种你不想捕获的错误. -
最小化
try/except
代码块中的代码量.try
的范围越大, 就越容易把你没想到的那些能抛出异常的代码囊括在内. 这样的话,try/except
代码块就掩盖了真正的错误. -
用
finally
表示无论异常与否都应执行的代码. 这种写法常用于清理资源, 例如关闭文件.
全局变量
避免使用全局变量.
嵌套/局部/内部类和函数
可以谨慎使用. 尽量避免使用嵌套函数和嵌套类, 除非需要捕获 self
和 cls
以外的局部变量. 不要仅仅为了隐藏一个函数而使用嵌套函数. 应将需要隐藏的函数定义在模块级别, 并给名称加上 _
前缀, 以便在测试代码中调用此函数.
推导式和生成式
可以用于简单情况. 以下每个部分不应超过一行: 映射表达式、for语句和过滤表达式. 禁止多重for语句和多层过滤. 情况复杂时, 应该用循环.
默认迭代器和操作符
只要是支持的类型 (例如列表、字典和文件), 就使用默认迭代器和操作符. 内置类型也定义了一些返回迭代器的方法. 优先使用返回迭代器的方法, 而非返回列表的方法, 不过注意使用迭代器时不能修改容器.
生成器
可以使用. 生成器的文档字符串中应使用”Yields:”而不是”Returns:”.
(译者注: 参看 注释 )
如果生成器占用了大量资源, 一定要强制清理资源.
一种清理资源的好方法是用上下文管理器包裹生成器 PEP-0533.
Lambda函数
适用于单行函数. 如果函数体超过60-80个字符, 最好还是定义为常规的嵌套函数.
对于乘法等常见操作, 应该用 operator
模块中的函数代替lambda函数. 例如, 推荐用 operator.mul
代替 lambda x, y: x * y
.
条件表达式
适用于简单情况. 以下每部分均不得长于一行: 真值分支, if 部分和 else 部分. 情况复杂时应使用完整的if语句.
默认参数值
可以使用, 不过有如下注意事项:
函数和方法的默认值不能是可变对象.
特性 (properties)
允许使用特性. 但是, 和运算符重载一样, 只能在必要时使用, 并且要模仿常规属性的存取特点. 若无法满足要求, 请参考 设置器和写入器 的规则.
举个例子, 一个特性不能仅仅用于获取和设置一个内部属性: 因为不涉及计算, 没有必要用特性 (应该把该属性设为公有). 而用特性来限制属性的访问或者计算 简单 的衍生值则是正确的: 这种逻辑简单明了.
应该用 @property
装饰器 (decorator) 来创建特性. 自行实现的特性装饰器属于威力过大的功能.
特性的继承机制难以理解. 不要用特性实现子类能覆写 (override) 或扩展的计算功能.
True/False的求值
尽可能使用”隐式”假值, 例如: 使用 if foo:
而非 if foo != []:
. 不过还是有一些注意事项需要你铭记在心:
-
一定要用
if foo is None:
(或者is not None
) 来检测None
值. 例如, 如果你要检查某个默认值为None
的参数有没有被调用者覆盖, 覆盖的值在布尔语义下可能也是假值! -
永远不要用
==
比较一个布尔值是否等于False
. 应该用if not x:
代替. 如果你需要区分False
和None
, 你应该用复合表达式, 例如if not x and x is not None:
. -
多利用空序列(字符串, 列表, 元组)是假值的特性. 因此
if not seq:
比if len(seq):
更好,if not seq:
比if not len(seq):
更好. -
处理整数时, 使用隐式false可能会得不偿失(例如不小心将
None
当做0来处理). 你可以显式比较整型值与0的关系 (len()
的返回值例外).正确:
if not users: print('无用户') if i % 10 == 0: self.handle_multiple_of_ten() def f(x=None): if x is None: x = []
错误:
if len(users) == 0: print '无用户' if not i % 10: self.handle_multiple_of_ten() def f(x=None): x = x or []
-
注意, ‘0’(字符串, 不是整数)作为布尔值时等于
True
. -
注意, Numpy 数组转换为布尔值时可能抛出异常. 因此建议用 .size 属性检查
np.array
是否为空 (例如if not users.size
).
词法作用域(Lexical Scoping, 又名静态作用域)
函数与方法装饰器
仅在有显著优势时, 审慎地使用装饰器. 装饰器的导入和命名规则与函数相同. 装饰器的pydoc注释应清楚地说明该函数是装饰器. 请为装饰器编写单元测试.
避免装饰器自身对外界的依赖(即不要依赖于文件, 套接字, 数据库连接等), 因为执行装饰器时(即导入模块时. pydoc
和其他工具也会导入你的模块) 可能无法连接到这些环境. 只要装饰器的调用参数正确, 装饰器应该 (尽最大努力) 保证运行成功.
装饰器是一种特殊形式的”顶级代码”. 参见关于 Main 的章节.
不得使用 staticmethod
, 除非为了兼容老代码库的 API 不得已而为之. 应该把静态方法改写为模块级函数.
仅在以下情况可以使用 classmethod
: 实现具名构造函数(named constructor); 在类方法中修改必要的全局状态 (例如进程内共享的缓存等)。
线程
虽然Python的内置类型表面上有原子性, 但是在特定情形下可能打破原子性(例如用Python实现 __hash__
或 __eq__
的情况下). 因此它们的原子性不可靠. 你也不能臆测赋值是原子性的(因为赋值的原子性依赖于字典的原子性).
选择线程间的数据传递方式时, 应优先考虑 queue
模块的 Queue
数据类型. 如果不适用, 则使用 threading
模块及其提供的锁原语(locking primitives). 如果可行, 应该用条件变量和 threading.Condition
替代低级的锁.
威力过大的特性
避开这些特性.
可以使用那些在内部利用了这些特性的标准模块和类, 比如 abc.ABCMeta
, dataclasses
和 enum
.
现代python: from __future__ imports
from __future__ imports
鼓励使用 from __future__ import
语句. 这样, 你的源代码从今天起就能使用更现代的 Python 语法. 当你不再需要支持老版本时, 请自行删除这些导入语句.
如果你的代码要支持 3.5 版本, 而不是常规的 >=3.7
, 请导入:
from __future__ import generator_stop
详情参见 Python future 语句 的文档.
除非你确定代码的运行环境已经足够现代, 否则不要删除 future 语句. 即使你用不到 future 语句, 也要保留它们, 以免其他编辑者不小心对旧的特性产生依赖.
在你认为恰当的时候, 可以使用其他来自 from __future__
的语句.
代码类型注释
强烈推荐你在更新代码时启用 python 类型分析. 在添加或修改公开API时, 请添加类型注释, 并在构建系统(build system)中启用 pytype. 由于python静态分析是新功能, 因此一些意外的副作用(例如类型推导错误)可能会阻碍你的项目采纳这一功能. 在这种情况下, 建议作者在 BUILD 文件或者代码中添加一个 TODO 注释或者链接, 描述那些阻碍采用类型注释的问题.