PEP8 和 PEP257 中文翻译版
说在前面
这是为我所在的实验室而准备的代码风格指南~
本代码风格指南此版本基本完全基于 $PEP8$、$PEP257$,未涉及部分都以其为准。
本代码风格指南默认为 $Python3$,对 $Python2$ 若有差异则会特殊声明。
代码布局
缩进
每级缩进为 $4$ 个空格
续行缩进有两种方式:括号内隐式垂直对齐或者悬挂缩进。
在使用悬挂缩进时,第一行不应该有参数,且应该用进一步的缩进来明确区分自己是续行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16# 正确: # 括号隐式对齐 foo = long_function_name(var_one, var_two, var_three, var_four) # 进一步缩进以区分 def long_function_name( var_one, var_two, var_three, var_four): print(var_one) # 悬挂缩进 foo = long_function_name( var_one, var_two, var_three, var_four)1 2 3 4 5 6 7 8 9 10 11# 错误: # 第一行有参数时应该使用括号内隐式对齐 foo = long_function_name(var_one, var_two, var_three, var_four) # 悬挂缩进需要进一步缩进以区分 def long_function_name( var_one, var_two, var_three, var_four): print(var_one)对于悬挂缩进,$4$ 空格缩进规则是可选的
1 2 3 4# 悬挂缩进并不一定要 4 个空格 foo = long_function_name( var_one, var_two, var_three, var_four)当 $if$ 语句的条件部分长到需要分行书写时,有多种规范可选
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16# 不额外缩进 if (this_is_one_thing and that_is_another_thing): do_something() # 使用注释区分 # 支持语法高亮 if (this_is_one_thing and that_is_another_thing): # 如果条件都为真,那么我们可以 frobnicate do_something() # 续行增加额外缩进 if (this_is_one_thing and that_is_another_thing): do_something()多行结构的结尾小括号/中括号/花括号可以放在一行的开头,也可以进行缩进
1 2 3 4 5 6 7 8my_list = [ 1, 2, 3, 4, 5, 6, ] result = some_function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', )1 2 3 4 5 6 7 8my_list = [ 1, 2, 3, 4, 5, 6, ] result = some_function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', )
制表符($Tab$)还是空格?
空格是推荐的缩进方式,除非原项目使用 $Tab$ 否则不应该使用。
禁止空格与 $Tab$ 混用。
但如今市面上大多数 $IDE$ 会将 $Tab$ 替换成 $4$ 个空格,使用时需注意即可。
最大行长度
所有行不应该超过 79 个字符。
对于结果限制较少的流长文本块($docstring$ 或注释),行长应限制在 72 个字符。
包装长行的首选方式是使用括号自带的隐含续行符。
通过用括号包装表达式,可以将长行分成多行。应优先使用这些括号,而不是使用反斜线续行。
有时仍需使用反斜杠,比如在 $Python 3.10$ 之前,多重 with-statements 长语句不能使用隐式续码,因此在这种情况下可以使用反斜线:
| |
除此之外还有断言语句 $assert$。
二元运算符处的换行
在二元运算符之前换行
| |
| |
空行
- 用两行空行将顶级函数和类的定义围起来
- 类内方法定义用一个空行包围
- 可少量使用额外空行来分割函数组
- 在函数中使用少量空行指示逻辑部分
- 总而言之,空行的作用就是使代码结构层次分明
源文件编码
$Python 2$ 默认 $ASCII$,$Python 3$ 默认 $UTF-8$。
对于 $Python3$ 无需指明代码,对于 $Python2$ 需指定编码格式为 $UTF-8$,
需要在文件顶端插入
| |
或者
| |
$import$
应该单行 $import$
1 2 3# 正确: import os import sys1 2# 错误: import sys, os但对于
from-import则可以单行1 2# 正确: from subprocess import Popen, PIPE$import$ 应该放在文件的顶部,在模块注释和 $docstring$ 之后,在模块全局和常量之前
$import$ 应该遵循以下顺序分组
- 标准库 $import$
- 相关第三方库 $import$
- 本地应用/库 $import$
每组之间应该用一行空格分隔。
推荐使用绝对导入,因为它们通常更具可读性,而且在导入系统配置不正确的情况下(如软件包内的目录最终出现在
sys.path),往往会表现得更好(或至少给出更好的错误信息)1 2 3import mypkg.sibling from mypkg import sibling from mypkg.sibling import example但应该避免复杂的布局并总是使用绝对导入
当从模块中导入类时,应该
1 2from myclass import MyClass from foo.bar.yourclass import YourClass如果这样导致与本地命名冲突,则应该
1 2import myclass import foo.bar.yourclass并且使用
myclass.MyClass和foo.bar.yourclass.YourClass避免使用通配符导入,就像避免使用
using namespace那样1 2# 避免使用 from <module> import *
模块级别 Dunder 名称
模块级别"$dunders$"(被两对下划线包裹的名称)应该被放在模块的 $docstring$ 和其他所有 $import$ 前,除了$ from \hspace{0.5em} __future__ \hspace{0.5em} import $,$Python$强制规定 future-imports 必须出现在除了 $docstring$ 外所有代码之前。
| |
字符串引号
在 Python 中,单引号字符串和双引号字符串是一样的。但是,当字符串包含单引号和双引号字符时,使用另一种引号以避免字符串中出现反斜线。这样可以提高可读性。
对于三重引号字符串,应始终使用双引号字符。
表达式和语句中的空格
$Pet \hspace{0.5em} Peeves$
以下情况中避免使用多余的空格:
紧靠圆括号
()、方括号[]、花括号{}1 2 3 4 5# Correct: spam(ham[1], {eggs: 2}) # Wrong: spam( ham[ 1 ], { eggs: 2 } )括号与逗号之间
1 2 3 4 5# Correct: foo = (0,) # Wrong: bar = (0, )逗号
,、分号;、冒号:前1 2 3 4 5# Correct: if x == 4: print(x, y); x, y = y, x # Wrong: if x == 4 : print(x , y) ; x , y = y , x例外的是,在切片中,冒号的作用类似于二进制运算符,两边的数量应该相等(将其视为优先级最低的运算符)。在扩展切片中,两个冒号必须有相同的间距。例外:当省略片段参数时,空格也会被省略:
1 2 3 4 5 6 7 8 9 10 11 12# Correct: ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] ham[lower:upper], ham[lower:upper:], ham[lower::step] ham[lower+offset : upper+offset] ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] ham[lower + offset : upper + offset] # Wrong: ham[lower + offset:upper + offset] ham[1: 9], ham[1 :9], ham[1:9 :3] ham[lower : : step] ham[ : upper]在函数调用的参数列表开头括号之前
1 2 3 4 5# Correct: spam(1) # Wrong: spam (1)开始索引的括号之前
1 2 3 4 5# Correct: dct['key'] = lst[index] # Wrong: dct ['key'] = lst [index]赋值(或其他)运算符之前
1 2 3 4 5 6 7 8 9# Correct: x = 1 y = 2 long_variable = 3 # Wrong: x = 1 y = 2 long_variable = 3
其他建议
避免在任何地方出现
trailing whitespace,结尾不要有多余的空白。这些二进制运算符的两边总是用一个空格包围:赋值 (
=)、增强赋值 (+=,-=等)、比较 (==,<,>,!=,<>,<=,>=,in,not in,is,is not)、布尔运算 (and,or,not)。如果使用不同优先级的运算符,可以考虑在优先级最低的运算符周围添加空格。不过,请自行判断,切勿使用一个以上的空格,而且二进制运算符两边的空格数量一定要相同:
1 2 3 4 5 6 7 8 9 10 11 12 13# Correct: i = i + 1 submitted += 1 x = x*2 - 1 hypot2 = x*x + y*y c = (a+b) * (a-b) # Wrong: i=i+1 submitted +=1 x = x * 2 - 1 hypot2 = x * x + y * y c = (a + b) * (a - b)函数注释应使用正常的冒号规则,如果存在
->箭头,则其周围必须有空格。1 2 3 4 5 6 7# Correct: def munge(input: AnyStr): ... def munge() -> PosInt: ... # Wrong: def munge(input:AnyStr): ... def munge()->PosInt: ...当
=符号用于表示关键字参数时,或用于表示未注明函数参数的默认值时,不要在其周围使用空格:1 2 3 4 5 6 7# Correct: def complex(real, imag=0.0): return magic(r=real, i=imag) # Wrong: def complex(real, imag = 0.0): return magic(r = real, i = imag)不过,在将参数注释与默认值相结合时,请在
=符号周围使用空格:1 2 3 4 5 6 7Correct: def munge(sep: AnyStr = None): ... def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ... # Wrong: def munge(input: AnyStr=None): ... def munge(input: AnyStr, limit = 1000): ...不鼓励使用复合语句(一行多个语句)
1 2 3 4 5 6 7 8 9 10# Correct: if foo == 'blah': do_blah_thing() do_one() do_two() do_three() # Wrong: if foo == 'blah': do_blah_thing() do_one(); do_two(); do_three()有时候,如果 if/for/while 语句的主体很小,可以将它们放在同一行。但是,对于包含多个子句的语句,不应该这样做。应该避免将过长的行折叠成多行。
不要这样:
1 2 3 4# Wrong: if foo == 'blah': do_blah_thing() for x in lst: total += x while t < 10: t = delay()绝对不要这样:
1 2 3 4 5 6 7 8 9 10 11# Wrong: if foo == 'blah': do_blah_thing() else: do_non_blah_thing() try: something() finally: cleanup() do_one(); do_two(); do_three(long, argument, list, like, this) if foo == 'blah': one(); two(); three()
何时使用 $Trailing \hspace{0.5em} Commas$
$Trailing \hspace{0.5em} Commas$ 通常是可选的,但在组成一个元素的元组时,$Trailing \hspace{0.5em} Commas$ 是必须的。为了清晰起见,建议用括号(从技术上讲是多余的)将后者包围起来:
| |
如果 $Trailing \hspace{0.5em} Commas$ 是多余的,那么在使用版本控制系统(如 $Git$)时,当预期值、参数或导入项的列表会随着时间的推移而扩展时,$Trailing \hspace{0.5em} Commas$ 通常会很有用。通常的做法是,将每个值(等)单独放在一行,始终加上一个 $Trailing \hspace{0.5em} Commas$,然后在下一行加上封闭括号/小括号/大括号。但是,将尾逗号与关闭分隔符放在同一行是不合理的(上述单元组的情况除外):
| |
注释
与代码相矛盾的注释比没有注释还要糟糕!当代码改变时总是先修改注释!
注释应该是完整的句子,以大写字母开头(除非是标识符)。
块注释通常由一或多段完整的句子构成,每个句子以句号结尾。
块注释
块注释通常适用于跟在它们后面的一些(或全部)代码,且与该代码有相同的缩进级别。块注释的每一行都以一个#和一个空格开始(除非它是注释内的缩进文本)。
块注释内的段落之间通过包含单个#的行进行分隔。
行注释
行内注释应该谨慎使用。
行内注释是在与语句相同的行上的注释。行内注释应至少与语句间隔两个空格。它们应以#和一个空格开始。
如果行内注释陈述的是显而易见的事情,那么它们就没有必要,甚至会分散注意力。不要这样:
| |
但有时候这是有用的:
| |
$Documentaton \hspace{0.5em} Strings$
$docstring$ 是作为模块、函数、类或方法定义中第一条语句出现的字符串字面量。这样的 $docstring$ 将成为该对象的 $__doc__$ 特殊属性。
所有模块,以及模块导出的所有函数和类都应有$docstring$。
本指南不涉及 $attribute \hspace{0.5em} docstrings$ 和 $additional \hspace{0.5em} docstrings$,如有需要请参考PEP 258。
为保持一致,在 $docstring$ 周围使用"""三重双引号""";如果要在 $docstring$ 中使用\反斜线,使用r"""raw三重双引号""";如果是 $Unicode \hspace{0.5em} docstring$,使用 u"""unicode三重双引号"""。
单行 $docstring$
单行 $docstring$ 适用于很简单很显然的情况
| |
需要注意:
即使 $docstring$ 只需要一行,也应该使用
"""三重双引号""",方便日后拓展;“““对于单行 $docstring$,三重双引号的开头和结尾应该在同一行”””
函数/方法的单行 $docstring$的前后都没有空行
应用祈使句的形式来规定函数或方法的效果(比如
Do this,Return that),而不是使用单纯的描述单行 $docstring$ 不应该重申函数/方法的参数(可通过内省获得),不要这么做:
1 2def function(a, b): """function(a, b) -> list"""这种 $docstring$ 的最佳形式应该是:
1 2def function(a, b): """Do X and return a list."""(
Do X应被替换为有用的描述)
多行 $docstring$
多行 $docstring$由摘要行($summary \hspace{0.5em} line$)、空行和更详细的说明组成。摘要行可能会被自动索引工具使用;重要的是,摘要行必须在一行内,并用空行与文档字符串的其他部分隔开。摘要行可以与开头引号在同一行,也可以在下一行。整个文档字符串的缩进与第一行的引号相同。
在所有描述类的 $docstring$ 后插入一个空行,也就是说,类 $docstring$ 和第一个方法之间要有一行空行。
脚本/独立程序的 $docstrig$ 应作为其使用信息使用,当脚本/独立程序被调用时,如果出现参数错误/缺失或者使用了 -h 选项,该 $docstring$ 会被打印出来。该 $docstring$ 应记录有脚本/独立程序的功能、命令行语法、环境变量和文件。使用信息可以相当详尽,应足以让新用户正确使用命令,并为老用户提供所有选项和参数的完整快速参考。简单地说,脚本/独立程序的 $docstrig$ 应作为其使用指南来写。
模块的 $docstring$ 应该列出该模块导出的类、错误、方法以及任何其他对象,并对每个类、异常和函数给出一行摘要。(这些摘要所提供的细节通常少于对象的 $docstring$ 中的摘要行)。包的 $docstring$(包的 __init__.py 中的 $docstring$ )应列出软件包导出的模块和子包。
函数或方法的 $docstring$ 应概述其行为,并记录其参数、返回值、副作用、异常情况以及调用时间限制(如适用)。应注明可选参数。应记录关键字参数是否是接口的一部分。
类的$docstring$应概述其行为,并列出公共方法和实例变量。如果该类打算被子类化(可被继承),并为子类提供了额外的接口,则应在文档中单独列出该接口。类的构造函数(__init__ 方法)应该在其自身的文档字符串中进行说明。单个方法应由它们自己的 $docstring$ 记录。
如果一个类继承自另一个类,并且其行为大部分继承自基类,那么其$docstring$应该提及这一点,并总结其中的区别,同时正确使用 override 和 extand。
不使用 $Emac$ 命名约定(不使用大写字母来表示函数或方法的参数)。Python 是区分大小写的,参数名可以用于关键字参数,所以文档字符串应该记录正确的参数名。在 $docstring$ 中,每个参数最好单独成行。例如:
| |
除非整个文档字符串可以放在一行中,否则请将结尾引号单独放在一行中。这样,就可以使用 Emacs 的 fill-paragraph 命令。
命名约定
首要原则
对用户可见的 API 的公共部分的命名应遵循反映使用情况而非实现的约定。也就是说,根据 API 的用途和功能来命名而不是内部实现。
描述性:命名风格
常见的命名风格有:
b(单个小写字母)B(单个大写字母)lowercase(小写)lower_case_with_underscores(小写下划线)UPPERCASE(大写)UPPER_CASE_WITH_UNDERSCORES(大写下划线)CapitalizedWords(大驼峰命名法,用此命名风格使用缩略词时,缩略词的所有字母都要大写。因此,HTTPServerError 好于 HttpServerError。)mixedCase(小驼峰命名法)Capitalized_Words_With_Underscores(丑!)_single_leading_underscore(前缀单下划线):弱 “内部使用 “指示符。例如,从 M import * 不能导入名称以下划线开头的对象。single_trailing_underscore_(后缀单下划线):通常用于避免与关键字冲突,如1tkinter.Toplevel(master, class_='ClassName')__double_leading_underscore(前缀双下划线):用于命名类属性,调用时这个命名会被改变(在类FooBar中,类属性__boo会变成_FooBar__boo,详见下文)。__double_leading_and_trailing_underscore__(前后双下划线):魔术对象或属性,存活于用户控制的命名空间内。比如__init__,__import__,和__file__。 永远不要自己起这样的命名。这些命名应仅在文档中使用。
规范:命名约定
应避免的命名
永远不要使用小写字母 l(el)、大写字母 O(oh)、大写字母 I(eye)作为单字母变量名!!!
包名和模块名
模块名应该是简短、全小写的名称,如果名称中是用下划线可以提高可读性,那么可以加入。
包名也应该是简短、全小写的名称,但是不鼓励使用下划线。
当 C/C++ 编写的扩展模块伴随一个提供更高级别接口的 $Python$ 模块时,$C/C++$ 模块命名应该以下划线开头(例如,_socket)。
类名
类名应该使用大驼峰命名法($CapWords$)。
如果类的接口主要是作为一个可调用的对象(主要调用 __call__()),那么该类的命名也可使用函数的命名规范,也就是全部小写、单词之间用下划线隔开。
类型变量名
NOT READY
异常名
因为异常一般是类,因此也适用类的命名规则。但是当异常是个错误时,则需要添加 Error 后缀.
全局变量名
(仅讨论只用于模块内部的全局变量)
其命名约定应与函数一致。
以 from M import * 的方式进行导入的模块应当使用 __all__ 机制来防止导入全局变量。或者采用旧版的约定,给模块中的全局变量增加一个先导下划线(表示该变量是仅限于模块内部全局使用,是非公共的)。
函数和变量名
函数名应该全小写,必要时是用下划线分割单词以提高可读性。
变量名与函数名遵守相同的命名约定。
除非项目本身使用的是小驼峰命名法,否则不使用。
函数和方法的参数
实例的方法的第一个参数总应该是 self。
类的方法的第一个参数总是 cls。
如果函数参数的名称与关键字冲突,如前文所述,应在参数名称后面加一个下划线。
方法名与实例变量
方法名与实例变量名称与函数命名规则一致即可。
使用前缀单下划线表示 non-public 的方法和实例变量。
如果为了避免和子类冲突,可以加前缀双下划线,这样在调用时会触发 $Python$ 的命名重整。
Python 中使用类名进行命名重整:如果 Foo 类有一个属性 __a,则调用时不能采用 Foo.__a 的形式(当然,如果硬要访问该属性,可以采用 Foo._Foo__a 的方式获取访问权)。一般来说,双前导下划线仅用于防止基类和子类的属性产生命名冲突。
常量
常量通常在模块级别定义,名称使用全大写并使用下划线分割单词。
继承的设计
设计时考虑方法和属性应该是 public 还是 non-public 。如果不能确定就按 non-public 设计,因为 non-public 转public 会更简单些。
$Python$ 中并没有真正的 private 属性,我们只会说 non-public。
设计时要明确好哪些是 public,哪些是子类的,哪些是仅限于基类的。
据以上内容,给出以下指南:
public属性不应该使用前缀下划线- 如果
public属性和关键字冲突,可添加后缀单下划线 - 对于简单的
public数据属性,最好直接公开属性名。$Python$ 提供了一个很方便的东西:修饰器。这种情况下,可以使用@property来把函数实现隐藏在简单的公共属性后面- 尽量在实现功能是避免副作用,尽管缓存之类的副作用没什么大问题。
- 避免对计算开销大的操作使用
property,这个属性标记会让调用者误以为是开销(相对)较低的操作。
- 如果你设计的类用于被继承,并且你希望一些属性不会被子类使用,可以在命名时采取双前缀下划线的方式。当该属性调用时会自动触发 Python 的命名重整,把类名放在属性名前面。这样即使我们无意中在子类使用了相同的名称,也可以避免属性名的冲突。
- 需要注意命名重整只是简单地利用了类名,所以如果子类类名和基类类名仍相同时,还是会构成命名冲突。
- 命名重整会使某些使用,比如调试和使用
__getattr__()不太方便。但有详细的文档说明和且很好手动操作。 - 不是所有人都喜欢命名重整,需要尝试平衡好避免命名冲突和被高级调用者调用这二者的需求。
公共接口和内部接口
只有公共接口才能保证向后兼容性。因此,用户能够清楚地区分公共和内部接口是很重要的。
有文档说明的接口被认为是公共的,除非文档明确声明它们是临时的或内部的,不受通常的向后兼容性保证的限制。所有没有文档说明的接口都应该被认为是内部的。
为了更好地支持自省,模块应该使用 __all__ 属性来明确声明它们的公共API中的名字。将 __all__ 设置为空列表表示该模块没有公共API。
即使 __all__ 设置得恰当,内部接口(包,模块,类,函数,属性或其他名字)仍然应该以单个下划线作为前缀。
如果包含一个接口的命名空间(包,模块或类)被认为是内部的,那么该接口也被认为是内部的。
导入的名字应该始终被认为是实现细节。其他模块不应该依赖于对这些导入名字的间接访问,除非它们是包含模块的API的明确文档化的一部分,比如os.path或者一个包的 __init__ 模块,它暴露了子模块的功能。
编程建议
代码的编写应该有利于其他 $Python$ 实现(PyPy, Jython, IronPython, Cython, Psyco 等等)。
- 例如,不要依赖于$CPython$对
a += b或a = a + b这样形式的字符串拼接语句的高效实现。这种优化即使在$CPython$中也是很脆弱的(只对某些类型有效),而且不是所有的Python实现都支持引用计数的。对于性能敏感的代码段,采用''.join()形式能保证在不同的Python实现中都可以在线性时间内完成字符串拼接。
- 例如,不要依赖于$CPython$对
与单例比如
None的比较,使用is或is not而不是等号和不等号并且需要注意,当你的意思是
if x is not None时,不要写if x,这并不等价。使用
is not运算符而不是not .. is,即使在功能上是一致的,但是前者具有更好的可读性1 2 3 4 5# Correct: if foo is not None: # Wrong: if not foo is None:在实现富比较排序操作是,最好把六种比较操作全部实现(
__eq__,__ne__,__lt__,__le__,__gt__,__ge__),而不是只依赖于某一种特定的比较操作。为最小化工作,$Python$ 提供
functools.total_ordering()装饰器,用于自动生成缺少的比较方法。这么做是因为解释器有可能会调换参数的位置,比如把
y > x替换为x < y。总是使用
def语句将lambda与标识符绑定而不是使用赋值语句。1 2 3 4 5# Correct: def f(x): return 2*x # Wrong: f = lambda x: 2*x第一种形式表示生成的函数对象的名称是 “
f",而不是通用的”<lambda>"。这对一般的回溯和字符串表示更有用。与显式def语句相比,赋值语句的使用消除了 lambda 表达式的唯一优点(即可以嵌入到更大的表达式中)。从
Exception类而不是BaseException类派生异常。从 BaseException 直接继承只适用于捕获异常几乎总是错误的异常。根据捕获异常的代码可能需要的区别来设计异常层次,而不是异常发生的位置。力求以编程方式回答 “出了什么问题?“的问题,而不是仅仅说明 “出现了一个问题”。(可参阅PEP 3151)
应在不丢失原始回溯的情况下使用
raise X from Y来表示显式替换。 在有意替换内部异常(使用raise X from None)时,应确保相关细节被转移到新异常中(例如在将KeyError转换为AttributeError时保留属性名称,或在新异常消息中嵌入原始异常的文本)捕获异常时,尽可能提及具体的异常,而不是使用简单的
except:子句:1 2 3 4try: import platform_specific_module except ImportError: platform_specific_module = None空的
except:子句将捕获SystemExit和KeyboardInterrupt异常,这将使Control-C中断程序变得更加困难,还会掩盖其他问题。如果想捕获所有提示程序错误的异常,使用except Exception(空的except:子句相当于except BaseException:)。一个好的经验法则是将空
except:子句的使用限制在以下两种情况中:- 如果
exception handler将打印或 log 记录回溯,至少让用户知道错误发生了。 - 如果代码需要做一些清理工作,但又要用
raise将异常向上传播。不过try...finally会是处理这种情况的更好方式。
- 如果
当捕获 $OS$ 错误时,最好使用 $Python 3.3$ 中引入的显式异常层次结构,而不是内省
errno值。对于所有的
try/except子句,将try子句限制在所需代码量的绝对最小值,这样可以避免掩盖错误。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15# Correct: try: value = collection[key] except KeyError: return key_not_found(key) else: return handle_value(value) # Wrong: try: # Too broad! return handle_value(collection[key]) except KeyError: # Will also catch KeyError raised by handle_value() return key_not_found(key)当资源是代码中某一特定部分的本地资源时,应使用
with语句来确保资源在使用后被及时可靠地清理。也可以使用try/finally语句。上下文管理器除了获取和释放资源外,还应通过单独的函数或方法来调用:
1 2 3 4 5 6 7# Correct: with conn.begin_transaction(): do_stuff_in_transaction(conn) # Wrong: with conn: do_stuff_in_transaction(conn)后一个示例没有提供任何信息来说明
__enter__和__exit__方法除了在事务结束后关闭连接外,还在做其他事情。在这种情况下,明确的信息非常重要。return语句要一致。函数中的return语句要么就都返回表达式,要么就都不返回。如果都要返回表达式,但是没有返回值的return语句,应当return None,并且在函数末尾(如果能抵达)也应该指明return语句。1 2 3 4 5 6 7 8 9 10 11 12# Correct: def foo(x): if x >= 0: return math.sqrt(x) else: return None def bar(x): if x < 0: return None return math.sqrt(x)1 2 3 4 5 6 7 8 9 10# Wrong: def foo(x): if x >= 0: return math.sqrt(x) def bar(x): if x < 0: return return math.sqrt(x)使用
''.startswith()和''.endswith()来替代使用切片来检查前缀和后缀。这样更简洁,也更不容易出错。1 2 3 4 5# Correct: if foo.startswith('bar'): # Wrong: if foo[:3] == 'bar':对象类型比较应始终使用
isinstance()而不是直接比较类型:1 2 3 4 5# Correct: if isinstance(obj, int): # Wrong: if type(obj) is type(1):对于序列(
str,List,Tuple),利用空序列为False这一事实:1 2 3 4 5 6 7# Correct: if not seq: if seq: # Wrong: if len(seq): if not len(seq):拒绝
trailing whitespace不要使用
==将布尔值和True或False比较:1 2 3 4 5 6 7 8# Correct: if greeting: # Wrong: if greeting == True: # Worse: if greeting is True:不鼓励在 try…finally 的 finally 套件中使用流程控制语句 return/break/continue,因为这样会使流程控制语句跳转到 finally 套件之外。这是因为此类语句将隐式地取消通过 finally 套件传播的任何活动异常:
1 2 3 4 5 6# Wrong: def foo(): try: 1 / 0 finally: return 42
函数注解
这部分很重要,但篇幅有限这里写不下了,详细请参阅 PEP 484。
类型注解
这部分很重要,但篇幅有限只能写下一小部分,详细请参阅 PEP 526 和 PEP 484,但 $PEP \hspace{0.5em} 484$ 应是首选语法。
模块级变量、类和实例变量以及局部变量的注解应在冒号后留一个空格,冒号前不需要空格。
如果右边有赋值,那么类型注解的两边各有一个空格
1 2 3 4 5 6 7# Correct: code: int class Point: coords: Tuple[int, int] label: str = '<unknown>'1 2 3 4 5 6 7# Wrong: code:int # No space after colon code : int # Space before colon class Test: result: int=0 # No spaces around equality sign