PEP: | 8 |
标题: | Python代码风格指南 |
作者: | Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Nick Coghlan <ncoghlan at gmail.com> |
状态: | 在行 |
类型: | 流程 |
创建时间: | 2001-07-05 |
发布历史: | 2001-07-05,2013-08-01 |
目录
简介
本文档为那些由Python主流发行版的标准库构成的Python代码给出了编码规范。您还可以参阅描述Python的C实现中的C代码的风格准则的姊妹PEP。
本文档和PEP 257(文档字符串约定)都改编自Guido原先写的Python Style Guide一文,并且补充了一些来自Barry的风格指南的内容。[2]
由于Python语言本身的变化,这个风格指南也会随着时间的推移而逐渐演变,一些新的约定会被确定,而一些旧的约定则会过时。
许多项目有它们自己的编码风格准则,如果这些准则与本文有所冲突,请在那个项目中优先遵守项目特定的准则。
枉曲直凑是小人的毛病
Guido的关键见解之一是:代码被阅读的次数比其被编写的次数更多。这里提供的准则旨在提高代码的可读性,并使大环境下的Python代码风格保持一致。正如PEP 20所说,“可读性至关重要”。
风格指南是关于一致性的文本。与本风格指南保持一致很重要,但项目内的一致性更重要、一个模块或函数内的一致性最重要。
然而,要知道何时应该不一致——有时候风格指南的建议正好是不适用的。当你对此困惑时,采用你的最佳判断下的方案。仔细看看其他例子的处理方法,再决定哪种做法看起来最好。而且,有困惑时不要犹豫,尽管向别人提问!
特别注意:不要仅仅为了遵守这个PEP就破坏向后兼容性!
忽视某条准则的其他一些充分理由:
- 即使对于那些习惯阅读遵循了本PEP的代码的人来说,当应用该准则会降低代码的可读性时。
- 与工作环境中的代码保持一致就会打破该准则(可能是由于历史原因)——尽管(在真正的XP*风格中)这也是个收拾别人烂摊子的好机会。
- 因为题中所述代码的完成时间早于该准则的首发时间,而且现在没有别的理由需要修改它。
- 当这段代码需要保持对旧版本的Python兼容,且该版本Python不支持这条准则所推荐使用的特性时。
*Extreme Programming,极限编程,译者注。
代码布局
缩进
每级缩进使用4个空格。
无论是在小括号、中括号和大括号里面垂直地使用Python的隐式行连接还是使用悬挂缩进[1],都应该对齐连续行内的被包裹元素。使用一个悬挂缩进时应考虑到以下要点;首行应当没有任何参数,且后续行应当使用更多的缩进来凸显它和首行的区别:
# 正确:
# 与起始定界符对齐
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 添加4个空格(额外的一级缩进)以区分参数和后续内容。
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)
# 错误:
# 当不使用垂直对齐时,首行禁止出现参数。
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空格规则是非强制的。
非强制的情况:
# 悬挂缩进*可能*缩进为4个空格以外的其他量。
foo = long_function_name(
var_one, var_two,
var_three, var_four)
当if语句的条件部分太长以至于需要分多行编写时,值得注意的是,一个由两个字符组成的关键字(即if)加上一个空格、再加上一个开的小括号就为多行条件的后续行创建了一个自然的4空格缩进。这样可以为嵌套在if语句中本该缩进4个空格的缩进代码块制造视觉冲击。这个PEP没有对怎样(或者是否应当)进一步在视觉上区分这种if语句的嵌入语块中的条件行作出明确解释。可选的处理方式包括但不限于:
# 没有额外的缩进。
if (this_is_one_thing and
that_is_another_thing):
do_something()
# 添加一个注释,这样就能在支持语法高亮的编辑器中增加一点辨识度。
if (this_is_one_thing and
that_is_another_thing):
# 当两个条件都为真时,我们可以开始恶搞。
do_something()
# 为条件部分的连续行添加额外的缩进。
if (this_is_one_thing
and that_is_another_thing):
do_something()
(另请参阅下面关于是否应当在二元操作符之前或之后断行的讨论。)
多行结构中闭的小括号/中括号/大括号可能置于序列末行的首个非空白字符的位置,例如:
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
或者也可以把它放在多行结构首行的第一个字符下方,例如:
my_list = [
1, 2, 3,
4, 5, 6,
]
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
制表符还是空格?
空格是首选的缩进方法。
制表符只用于与那些早已使用制表符来缩进的代码来保持一致。
Python不允许混用制表符和空格来缩进。
最大行长度
所有行都限制为最多含有79个字符。
对于那些较少结构限制的可流动的长文本块(文档字符串或者注释),其行长度应当限制到72个字符。
限制编辑器所需的窗口宽度让你能够同时打开几个文件,并且让你得以正常使用那些并列展示两个版本的代码审查工具。
大多数工具的默认折叠会破坏代码的可视结构,让代码更难以理解。选择这样的限制上限之后,即使编辑器在折叠代码行时会在最后一列添加标记,也能避免代码在窗口宽度设置为80的编辑器中产生折叠。当然,一些基于web的工具可能根本不具备动态折叠代码的功能。
一些团队特别衷爱更长的行长度。如果代码主要或专门由一个部可以对此问题达成一致团队来维护,那么把行长度的上限增加到99个字符也未尝不可,但前提是保证注释和文档字符串的仍然在72个字符的位置就折叠到下一行。
Python标准库是一个保守的库,它要求将行限制在79个字符之内(而文档字符串/注释则是72个字符)。
折叠长行的首选方式是在小括号、中括号和大括号中使用Python的隐式续行。可以通过将表达式折叠到括号中的方法来把长行分散为多行。相比使用反斜杠,这些方法应当作为续行的首选。
有时,反斜杠可能也是合适的。例如,在Python 3.10之前,多with语句不能使用隐式续行,因此反斜杠适用于这种情况:
with open('/path/to/some/file/you/want/to/read') as file_1, \
open('/path/to/some/file/being/written', 'w') as file_2:
file_2.write(file_1.read())
(参见之前关于多行if语句的讨论并进一步思考类似的with语句的缩进。)
另一个像这样的例子是assert语句。
确保对连续行进行适当缩进。
断行符应该在二元操作符之前还是之后?
数十年来的推荐风格都是在二元操作符之后断行,但是这样会从两方面降低代码的可读性:一是二元操作符会倾向于分布在屏幕上的不同列,二是每个操作符会从对应的操作数那里被移走并放到上一行。如此一来,你不得不费更多工夫来辩别哪一项被加、哪一项被减:
# 错误
# 操作符位于远离被操作数的位置。
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
为了解决这个影响可读性的问题,数学家及其编辑们采用了与上文相反的约定Donald Knuth在他Computers and Typesetting series这本书中对此作出了解释:“尽管行内公式通常在二进制操作符和关系符号之后断行,但行间公式却总是在二元操作符之前断行”[3]。
遵循这一来自数学家的传统之后,通常可以编写出可读性更好的代码:
# 正确:
# 易于匹配二元操作符及其被操作数。
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
Python代码中,只要和原先约定保持一致,在二元操作符之前或之后断行都是允许的。但对于新编写的代码,我们推荐采用Knuth的风格。
空行
顶级函数和顶级类的定义之间用两个空行隔开。
类内部的方法定义之间用一个空行隔开。
可以(谨慎地)用额外的空行对相关的函数进行分组。应该省略一组相关的单行代码(例如一组虚拟实现的语句)中的空行。
在函数之间少量使用空行来指出逻辑上关联的部分。
Python接受将control-L(即^L)换页符作为空白使用,许多工具将其视为页面分隔符号,因此你可以用它来对你文件中的关联部分进行分页。注意,一些基于web的代码阅读器可能不会将control-L视为换页符,而是在这个位置显示别的标记。
源文件编码
Python核心发行版中的代码应该始终使用UTF-8编码,而且不应有任何编码声明。
在标准库中,非UTF-8编码应当仅作测试之用。
谨慎使用非ASCII字符,最好仅将其用于表示地名和人名。如果使用了非ASCII字符作为数据,避免使用像“z̯̯͡a̧͎̺l̡͓̫g̹̲o̡̼̘”这样的噪声Unicode字符和字节顺序标记。
Python标准库中的所有标识符都必须是仅含ASCII字符的标识符,而且应该在可行的地方都使用英文(在许多情况下,缩写和术语使用的并非英语)。
开源项目面向全球,鼓励采用与前文所述相似的编码策略。
导入
- 导入通常应当被编写在分立的行上:
# 正确:
import os
import sys
# 错误:
import sys, os
这么做也是可以的:
# 正确:
from subprocess import Popen, PIPE
- 导入往往被置于文件的顶部,即所有模块注释和文档注释之后,以及模块全局变量和常量之前。
导入应当按照以下顺序依次进行:
- 标准库导入。
- 相关第三方库导入。
- 本地特定的应用/库导入。
你应当在每组导入之间空一行。
- 推荐使用绝对导入,如果导入的系统配置不正确的话(比如包中的目录在sys.path结束时),这样通常能让代码更易读且倾向于有更好的表现(至少能给出更好的错误提示):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
不过,相对于绝对导入,显式相对导入也是可供你选择的选项,尤其是在处理那些用绝对导入会导致不必要的罗嗦的那些复杂布局的包的时候:
from . import sibling
from .sibling import example
标准库的代码应当避免复杂的包布局并始终使用绝对导入。
- 当从包含类的模块中导入一个类时,通常可以这么表述:
from myclass import MyClass
from foo.bar.yourclass import YourClass
如果这个语句导致了本地名称冲突,那么可以隐式地表述它:
import myclass
import foo.bar.yourclass
然后即可使用“myclass.MyClass”和“foo.bar.yourclass.YourClass”。
- 应当避免使用通配符导入(from <module> import *),因为这样会让人搞不清楚命名空间中存在哪些名称,让读者和自动工具都难以理解。有一个使用通配符导入的特例,那就是将内部接口作为公共API的一部分重新发布时(例如,选用一个加速模块的定义来覆盖一个纯Python实现的接口时,哪些定义会被覆盖是事先无法预料的)。
用这种方式重新发布名称时,仍然适用以下关于公共接口和内部接口的准则。
模块级Dunder名称
模块级“Dunder*”(即那些前后都有两条下划线的名称)如__all__、__author__、__version__等,都应当放在文档字符串之后,以及除了from __future__ 导入以外的导入语句之前。Python要求模块中的future导入必须出现在除了文档字符串以外的代码之前:
"""这是示例模块。
这个模块具有一定功能。
"""
from __future__ import barry_as_FLUFL
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'
import os
import sys
*“dunder”可意译为“双下”,即“双下划线(double underlines)”的简称,国内一般译为“魔法变量/魔法函数”、“魔术变量/魔术函数”等。
字符串引号
在Python中,单引号字符串和双引号字符串本质上是一样的。PEP对此不作建议,选定一个规矩并坚持使用就好。不过,当字符串中包含单引号或双引号字符时,通过使用另一种引号表示字符串来避免在字符串中使用反斜杠可以提高可读性。
对于三引号字符串,应当始终使用双引号字符来与PEP 257中的文档字符串约定保持一致。
表达式和语句中的空白
恶习
应当避免在下列情形中出现多余的空格:
- 紧跟小括号、中括号、大括号的内部:
# 正确:
spam(ham[1], {eggs: 2})
# 错误:
spam( ham[ 1 ], { eggs: 2 } )
- 尾随逗号和其后的闭括号之间:
# 正确:
foo = (0,)
# 错误:
bar = (0, )
- 紧跟逗号、分号或冒号之前:
# 正确:
if x == 4: print x, y; x, y = y, x
# 错误:
if x == 4 : print x , y ; x , y = y , x
- 不过,冒号在切片中的作用类似二元操作符,其两侧应当有等量的空格(把它当作一个优先级最低的操作符)。在拓展切片中,两个分号都应该赋予等量的空格。例外:如果切片中省略了某个参数,则对应空格也应省略:
# 正确:
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]
# 错误:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
- 紧跟函数调用的参数列表开头的开括号之前:
# 正确:
spam(1)
# 错误:
spam (1)
- 紧跟索引或切片开头的开括号之前:
# 正确:
dct['key'] = lst[index]
# 错误:
dct ['key'] = lst [index]
- 赋值(或其他)操作符周围有多个空格以使其与另一个操作符对齐:
# 正确:
x = 1
y = 2
long_variable = 3
# 错误:
x = 1
y = 2
long_variable = 3
其他建议
- 避免在任何地方使用尾随空白。因为尾随空白它通常难以察觉,而且容易造成困扰:例如一个反斜杠后接一个空格和一个新行,那它就不算续行符了。有些编辑器不排斥这种做法,但许多有pre-commit hooks*的项目(比如CPython本身)会驳回这种做法。
*预提交钩子,用于代码检查,译者注。
- 始终在这些二元操作符两侧放一个空格:赋值号(=)、增量赋值号(+=、-=等), 比较符号(==、<、>、!=、<>、<=、>=、in、not in、is、is not)、布尔运算(and、or、not)。
- 如果使用不同优先级的操作符,可以考虑在最(较)低优先级的操作符周围添加空格。具体情况自行判断,但切勿使用多个空格,并且二元运算符的两侧应当始终具有数量相同的空格:
# 正确:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# 错误:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
- 函数的声明应当使用冒号的一般规则,如果出现箭头->的话,应始终确保箭头前后有空格。(更多关于函数声明的内容参见后续的函数注解):
# 正确:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...
# 错误:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
- 指出关键词的参数值或给未注解的函数参数设置默认值时,不要在=符号前后使用空格:
# 正确:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# 错误:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
但用于连接有默认值的参数注解时,要在在=符号前后使用空格:
# 正确:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
# 错误:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
- 一般不鼓励使用复合语句(同一行内有多个语句的语句):
# 正确:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()
而不是:
# 错误:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
- 但有时把短的if/for/while语句放在同一行也是可以的,但对多从句的语句就别这么做,反而还要避免折叠这么长的行!
而不是:
# 错误:
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
更不应该:
# 错误:
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()
何时使用尾随逗号
尾随逗号通常是非强制的,除非在创建一个元素的元组时才是强制的。为了清楚起见,建议将后者用括号括起来(技术上是多余的):
# 错误:
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()
在使用版本控制系统的情况下,如果值、参数或者导入项需要随着时间的推移而进行拓展,冗余的尾随逗号一般都很有用。操作方法就是将每个值(之类的)单独放一行,并始终对其追加一个尾随逗号,最后在序列的最后一行添上闭的小括号、中括号或大括号。但是尾随逗号和结束定界符在同一行是没有意义的(除了上述单元组的情况):
# 正确:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
# 错误:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
注释
与代码相矛盾的注释比没有注释更糟糕。当代码更新时,要始终优先保证注释也是最新的!
注释应当是完整的句子。注释的首字母应当大写,除非句子的第一个单词是以小写字母开头的标识符(千万不要改变标识符的大小写!)。
块注释通常由一个或多个以整句为基础的段落组成,其中每个句子以句号结尾。
在多句注释中,除了最后一句之外,你应当每一句结束的句号后加入两个空格。
确保你写的注释对于你所用语言*的其他使用者来说清晰易懂。
来自非英语国家的Python程序员:请用英语书写你的注释,除非你120%确定你的代码不会被那些不懂你的语言的人读到。
*特指英语、汉语等在社会交际中使用的自然语言,而不是用于人机通信的编程语言,译者注。
块注释
块注释一般用于注释其后续的部分(或全部)代码,并且和被注释代码使用相同的缩进等级。块注释的每一行都以#和一个空格开头(除非注释中有缩进文本)。
块注释中的段落用含有一个#的空行分隔。
内联注释
尽量少用内联注释。
内联注释是和语句在同一行上的注释。应当使用至少两个空格来将内联注释和前面的语句分隔开。内联语句应当以一个#和一个空格开头。
如果语句显而易见的话,内联注释就是不必要的,而且它反而会让人分心。不要这样做:
x = x + 1 # 累加 x
但有时,这么做挺有用:
x = x + 1 # 弥补边框
文档字符串
写好的文档字符串(又叫“docstrings”)的约定早已记载在PEP 257。
- 为所有的公共的模块、函数、类和方法都编写文档字符串。非公共的方法没有必要写文档字符串,但应当有一段注释来说明描述函数的作用。而且这个注释还应当写在def行后面。
- PEP 257 阐述了良好的文档字符串该遵守的约定。最需要注意的是,结束多行文档字符串的"""应当处于单独的一行。
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
- 请确保单行文档字符串的闭"""和字符串在同一行:
"""Return an ex-parrot."""
命名约定
Python库的命名约定有一点混乱,所以我们也从来没打算让它们完全保持一致——不过现行的命名标准建议倒还是有的。新写的模块和包(包括第三方框架)都应当按照这些标准来编写,不过要是已有的库的风格与此有所不同的话,那还是应该优先保持库内部的一致性。
根本原则
作为API的公共部分,用户可见的名称应当遵循这样一个约定,即名称应当反映其用法而不是其实现方法。
描述性原则:命名风格
目前有很多不同的命名风格,这些风格的提出有助于在忽略具体被命名对象的情况下让人认出该对象使用了什么风格的命名方式。
下面这些命名风格一般很容易认出来:
- b(单个小写字母)
- B(单个大写字母)
- lowercase(全小写)
- lower_case_with_underscores(小写带下划线)
- UPPERCASE(全大写)
- UPPER_CASE_WITH_UNDERSCORES(全大写带下划线)
- CapitalizedWords(单词首字母大写,也叫 CapWords(大写单词)或者CamelCase(驼峰命名法)——因为其字母高低不平而得名[4]。这种方法有时还冠以StudlyCaps(帅气大写)为人所熟知。)
- 注意:如果CapWords里面有缩写,请把缩写词的每个字母都大写。因此HTTPServerError的写法比HttpServerError更好。
- mixedCase(大小写混合,和CapitalizedWords的区别在于mixedCase的第一个字母是小写!)
- Capitalized_Words_With_Underscores(单词首字母大写带下划线,真丑!)
还有一种用简短而独特的前缀来把相关的名称组合在一起的风格。这种风格在Python中用得不多,不过还是顺带提一下,这种做法是为了完整性。例如,os.stat()函数会返回一个元组,其元素的名称通常有像st_mode、st_size、st_mtime这样的名称。(这么做是为了强调这些元素与POSIX系统调用框架的范围一致,有助于让程序员意识到这隐含信息。)
X11库用X来引导其所有公共函数。在Python中,一般认为没有必要使用这种风格,因为特性和方法的名称都以对象名为前缀,而函数名又以模块名为前缀。
此外,下面这种以下划线引导或尾随的特殊形式也很容易辨认(它们通常可以与其他命名风格的约定搭配使用)。:
- _single_leading_underscore(单前导下划线):弱“内部使用”标识。例如from M import *不会导入以下划线开头的对象。
- single_trailing_underscore_(单尾随下划线):约定用于避免和Python的关键字冲突,例如tkinter.Toplevel(master, class_='ClassName')。
- __double_leading_underscore(双前导下划线): 当命名类的特性时,调用名称矫正(类FooBar中,__boo会变成_FooBar__boo;更多请参见下文)。
- __double_leading_and_trailing_underscore__(双前导下划线和尾随下划线):用户控制的命名空间中的“魔术”对象或特性。例如__init__、__import__或__file__。不要自己创造这样的名称,只能使用已记录的那些双下划线名称。
强制性原则:命名约定
应当避免的名称
不要单独使用“l”(字母L的小写)、“O”(字母o的大写)或者“I”(i的大写字母)这样的字符作为变量名称。
在有一些字体中,这些字符很难和数字1跟0区分开来。打算用“l”的时候,用“L”来代替。
ASCII兼容性
标准库中使用的标识符必须如PEP 3131中描述的方针部分那样与ASCII相兼容。
包和模块的名称
模块应当用短、全小写的名称。如果可以提高可读性的话,也可以在模块名中使用下划线。Python包也应当使用短、全小写的名称,但不推荐使用下划线。
如果一个用C或者C++编写的模块附带一个提供了更高级(例如面向更多对象)接口的Python模块,那个C/C++模块的名称应当有前导下划线(例如_socket)。
类的名称
类的名称通常使用CapWords的约定。
在接口被记录且主要用于可调用对象的情况下,可以用函数的命名约定来代替类的命名约定。
注意,内置名称有其独立的约定:因为CapWords的约定仅适用于异常名的称和内置常数的名称,绝大多数内置名称是单词(或者几个词连在一起)。
类型变量的名称
相比短名称,PEP 484中所述类型变量的名称一般应当使用CapWords风格:T、AnyStr、Num。建议给变量添加后缀来指出其在协变或逆变行为上的一致性:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
异常的名称
因为异常是一种类,类的命名约定适用于此。但是你应当使用“Erro”作为你的异常名称的后缀(如果那个异常确实是一个错误的话)。
全局变量的名称
(我们首先假设这些变量仅在一个模块内部使用。)这些规定跟函数的是一样的。
计划通过from M import *来使用的模块应该使用__all__机制来防止导出全局变量,或者使用更旧的约定,即在这些(你想表明是“模块的而非公共的”的)全局变量前面添加下划线(你可能想要这样做,以表明这些全局变量是“模块非公共的”)。
函数和变量的名称
函数名应当用lowercase的约定,必要时可以添加下划线分隔单词来提高可读性。
变量名称遵循和函数名称一样的约定。
mixedCase风格只允许在已经流行该风格的上下文(如threading.py)中使用,以保持向后兼容性。
函数和方法的参数
始终为实例方法的第一个参数使用关键字self。
始终为类方法的第一个参数使用关键字cls。
如果一个函数的参数名称与保留关键字相冲突,一般最好是在这个参数关键字后面加下划线,而不是使用缩写或者错误的拼写。因此class_的命名方式比clss更好。(或许用近义词来避免冲突更好。)
方法名称和实例变量
使用函数的命名规则: lowercase以及必要时提高可读性的下划线。
仅对非公共方法和实例变量使用前导下划线。
为了避免名称和子类相冲突,使用双前导下划线来调用Python的名称矫正规则。
Python会矫正那些和类名称冲突的名称:如果Foo类有一个叫__a的特性的话,它就不会被Foo.__a访问。(固执的用户还仍然可以通过调用Foo._Foo__a来访问该特性。)一般,双前导下划线仅用于避免和那些子类专属的特性相冲突。
注意:使用__名称是有所争议的(详见下方)。N
常量
常量通常在模块级进行定义,并用带有分词下划线的全大写字母来表示。例如MAX_OVERFLOW和TOTAL。
继承的设计
始终确定好类的方法和实例变量(包括“特性”)是否应该公共化。如果不太确定的话,选择不共化,后续把一个特性改为共化比改为不共化要麻烦。
公共特性是给那些与你的类不太相关的客户所使用的特性,你得确保它们不会发生向后兼容不兼容的变更。非公共特性是那些不打算被第三方使用的特性,你不必确保非公共特性将来不会改变或者被移除。
在这里,我们不使用“private(私有的)”,因为在Python中没有任何一个(未经过大量必要工作处理过的)变量是真正私有的。
另一种特性是那种“子类API”的一部分(在其他语言里一般称为“protected(保护特性)”)。有些类就是用来继承的,要么拓展要么更改这些类某方面的行为。设计这些类的时候,注意明确哪些特性是公共特性、哪些特性是子类API的一部分、哪些是真正仅供你的基础类使用的特性。
出于这种考虑,Python制定了以下准则:
- 公共特性不应有任何前导下划线。
- 如果公共特性与保留字有冲突,则为你的特性名称添加一个尾随下划线。这种做法比用缩写或者错误的拼写要更好。(然而,尽管有这个规则,对于已经为人所熟知的任何代指一个类的变量或参数,尤其是对于类方法的第一个参数而言,“cls”还是其首选的拼写方式。)
注1:参见以上关于类方法的参数名称推荐的讨论。
- 对于简单的公共数据特性,最好只公开特性名称,而不要涉及复杂的访问器/更改器方法。如果您发现一个简单的数据特性需要发展功能性的行为,请记得Python为其后续增强提供了简单的途径。在这种情况下,请使用属性来隐藏简单数据特性访问语句背后的功能实现。
注1:尽量保持功能性的行为没有副作用,尽管像这样的副作用缓存通常是好的。
注2:避免对计算开销较大的操作使用属性,因为那个特性符号*会使得访问器认为这个访问的开销(相对)较小。
*“特性符号”即点符号,又称“点标记”,译者注。
- 如果你的类会被子类化,而且你还有一些不希望子类使用的特性,那么可以考虑用含有双前导下划线而不含尾随下划线的方式来命名这些类。这样会调用Python的名称矫正算法,这个算法会把类的名称矫正为特性的名称。如果子类在无意中使用了与特性一样的名称,这么做可以避免其与特性名称的冲突。
注1:注意名称矫正仅用于对简单的类名称进行矫正,因此如果一个子类同时用了已有类和特性的名称,那还是会发生名称冲突。
注2:名称矫正会给诸如调试和_getattr__()这样的特定用途带来一定不便。不过名称矫正算法有重组的文档支持,而且易于手动执行。
注3:并不是所有人都喜欢名称矫正。类的名称尽量在避免异常名称冲突的需求和被高级调用者所使用的潜在需求之间取得平衡。
公共和内部接口
确保任何向后兼容性只应用于公共接口。相应地,用户能够清楚地分辨公共和内部接口也很重要。
文档所述的接口都理应是公共接口,除非描述文档明确地宣称它们是临时接口或内部接口,不受通常的向后兼容性保证的限制。所有未文档化的接口都应当假定是内部接口。
为了更好地支持自省机制,模块应当使用__all__特性来明确地指出其属于公开API的名称。将__all__设置为一个空列表则表明那个模块没有公共API。
即使合理地设置了__all__特性,内部接口(包、模块、类、函数、特性或者其他对象的名称)也都应当以一个前导下划线作为前缀。
如果其包含的命名空间(包、模块或类)中有元素被视为内部元素,则该接接口也将被视为内部接口。
应当始终把导入的名称当作模块的一个实现细节来看待。其他模块不能依赖于间接访问这些导入的名称来实现,除非这些名称在模块包含的API文档中被明确记载要这么使用,例如os.path的名称或者包中用于公开子模块功能的__init__模块的名称。
编程建议
- 应当用以一种不会损害其他Python实现(如PyPy、Jython、IronPython、Cython、Psyco)的方式来编写代码。
- 例如,对a += b或a = a + b形式的语句,不要依赖CPython就地字符串连接的有效实现。即使在CPython中,这种优化也是脆弱的(它只对某些类型起作用),而且在不使用引用计数的实现中也根本不存在这种优化。库中性能容易受影响的部分应使用“.join()”形式来代替就地字符串连接。这样就能确保不同实现之间的串联发生在线性时间内。
- 像None这样的单值之间的比较,应当始终使用is或者is not来进行,而不要使用相等操作符。
- 而且,判断一个默认值是None的变量或参数是否已经被设置为其他值的时候——例如当你真的想表达if x is not None的意思时——注意if x的书写。其他值可能是一种会在布尔上下文中表现为假的数据类型(比如容器类型)。
- 使用is not操作符而不要使用not ... is操作符。虽然这两个表达式的功能是相同的,但前者可读性更好也更好:
# 正确:
if foo is not None:
# 错误:
if not foo is None:
- 当实现含有大量比较的排序操作时,最好实现所有六个操作符(__eq__、__ne__、__lt__、__le__、__gt__、__ge__)而不是依靠其它代码而只能进行某个特定的比较。
为了减小比较所涉及的计算量,functools.total_ordering()装饰器为生成Python所缺少的比较方法提供了一种手段。
PEP 207表明,自反性规则被Python所采用。因此,编译器可能会将y > x转换为x < y、y >= x转换为x <= y,而且可能会转换x == y和x != y的参数。sort()和min()操作可以确保使用<操作符,而max()函数则确保使用>操作符。尽管如此,最好还是实现全部六个操作,以免在在其他上下文中出现混乱。
- 始终使用def语句而不要使用直接在标识符后面带一个lambda表达式的赋值语句:
# 正确:
def f(x): return 2*x
# 错误:
f = lambda x: 2*x
第一种形式意味着所得的函数对象的名称是具体的“f”,而不是一般性的“<lambda>”。一般来说,在回溯和字符串表示时,这么做更有用。赋值语句的使用抹杀了lambda表达式相对于显式def语句的唯一好处(即它可以嵌入到更大的表达式中)
- 从Exception而不是BaseException中派生异常。直接从BaseException中继承的手段是为那些一旦捕捉就几乎意味着犯错的异常而保留的。
根据捕获异常的代码可能需要的差别来设计异常的层次结构,而不是根据异常被抛出的位置来设计。这么设计的目的在于用编程的方式回答“出了什么问题?”,而不是仅仅声明“发生了一个问题”(请参阅PEP 3151以学习本经验关于内建异常层次结构示例)。
类的命名约定也适用于此,不过,如果那个异常确实是一个错误的话,你还是应当为你的异常类添加“Erro”后缀。用于非本地流程控制或者其他形式的信号处理的、非错误的异常不需要特殊的后缀。
- 合理使用异常链。raise X from Y可被用于在不丢失原始回溯信息的同时明确地指出需要修改的代码。
当有意要(使用raise X from None)替换一个内部异常,务必确保相关的细节也被转移到了新的异常中(比如在将KeyError转换 AttributeError时,保留特性名,或者在新的异常消息中嵌入原始异常的文本)。
- 捕捉异常时,要尽可能提及详尽的异常,而不要使用空的except:子句:
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
一个空的except:子句会捕捉 SystemExit和SystemExit类型的错误,导致难以使用Control-C来中断程序,而且会掩盖程序中存在的其他问题。如果你想捕捉所有导致程错误的异常,请使用except Exception:(空的except相当于except BaseException:)。
一个很好的经验法则是将空“except”子句限定在以下两种情况中使用:
- 如果异常处理程序会打印或记录回溯信息;或者至少用户能察觉到有错误发生。
- 如果代码需要做一些清理工作,但是随后用raise抛出异常。这种情况用try...finally处理更好。
- 当捕捉操作系统的错误时,最好使用Python3.3中引进的显式异常层次,而不是使用errno值的自省。
- 此外,对于所有try/except子句,限制try子句的数量至代码所需的绝对最少量。同理,这样也能防止代码中藏有bug:
# 正确:
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
# 错误:
try:
# try的内容太宽泛了!
return handle_value(collection[key])
except KeyError:
# 不过依旧能捕捉从handle_value()抛出的KeyError异常。
return key_not_found(key)
- 当一个资源是某个特定代码段的局部时,请在它使用过后运用with语句来确保迅速可靠地将其清理掉。这种情况下也可以使用try/finally语句。
- 无论何时做了获取或释放资源以外的其他操作,都应当通过单独的函数或方法调用上下文管理器。例如:
# 正确:
with conn.begin_transaction():
do_stuff_in_transaction(conn)
# 错误:
with conn:
do_stuff_in_transaction(conn)
后面的例子并没有提供任何信息来表明__enter__和__exit__方法除了在事务之后关闭连接还做了什么额外工作。在这种场合,稍加明确是很重要的。
- 返回语句之间要保持一致。函数中的所有返回语句要么都有返回值,要么都没有返回值。只要存在一个返回语句有返回值,则任意一个没有返回值的返回语句都应该明确地以return None来表述该情况,并且应当把显式返回语句放在函数末尾(如果可以):
# 正确:
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)
# 错误:
def foo(x):
if x >= 0:
return math.sqrt(x)
def bar(x):
if x < 0:
return
return math.sqrt(x)
- 使用''.startswith()和''.endswith()代替字符串分割来测试前缀或后缀。
startswith()和endswith()表达起来更清晰,并且更能渐少错误:
# 正确:
if foo.startswith('bar'):
# 错误:
if foo[:3] == 'bar':
- 应当始终使用isinstance()进行对象类型的比较,而不是直接比较类型 :
# 正确:
if isinstance(obj, int):
# 错误:
if type(obj) is type(1):
- 对序列(如字符串、列表、元组)作判断时,利用好空序列为假的事实:
# 正确:
if not seq:
if seq:
# 错误:
if len(seq):
if not len(seq):
- 不要书写依赖重要尾随空格的字符串文字。这样的尾随空格在视觉上难以识别,一些编辑器(或者最近的reindent.py)会对这些空格进行裁剪。
- 不要用==将布尔值与True或False进行比较:
# 正确:
if greeting:
# 错误:
if greeting == True:
更坏的做法:
# 错误:
if greeting is True:
- 不鼓励在try...finally的finally套件中使用return/break/continue这几个会导致流程控制语句跳出finally套件的控制语句。这是因为这样的语句会隐式地中正在finally套件中传递的现用异常:
# 错误:
def foo():
try:
1 / 0
finally:
return 42
函数注解
随着PEP 484的采用,函数注解的风格规则已经变更:
- 函数注解应当使用PEP 484语法(前面的章节已经对函数注解的格式提出了一些建议)。
- 不再鼓励实践之前在本PEP中推荐的注解风格。
- 但是,现在鼓励在标准库之外的场合实践PEP 484的规则。例如,用PEP 484风格的注解来标记大型第三方库或者应用程序、反思添加这些注解是多么容易、观察注解的存在是否提高了代码的易懂性。
- Python的标准库应当保守地采用这样的注解,但可以在新代码和大型重构中(广泛地)使用它们。
- 对于想要以不同方式来使用函数注解的代码,建议在靠近文件顶行的位置添加如下格式的注释:
# type: ignore
这个注释告诉类型检查器忽略所有注解。(在PEP 484中可以找到更精确的方法来禁用类型检查器的反馈。)
- 和Linter*一样,类型检查器也是一个可选的、独立的工具。在默认情况下,Python解释器不会由于类型检查而发送任何消息,也不会根据注释更改其行为。
*一款Python代码检测插件,译者注。
- 不想使用类型检查器的用户可以随意忽略它们。然而,第三方库的用户们可能希望对这些包进行类型检查程序。为此,PEP 484建议使用存根文件——即.pyi文件,类型检查器会优先读取这些文件,而不是相应的.py文件。存根文件可以与库一起分发,也可以(在库作者的许可下)通过typeshed repo[5]单独分发。
变量注解
PEP 526 引入了变量注解。其风格建议和前文描述的函数注解是一样的:
- 模块级变量、类和实例变量,以及本地变量的注解都应当在冒号之后留一个空格。
- 冒号前不应有任何空格。
- 如果一个赋值号有右侧项的话,则等号的两侧都应该有一个空格:
# 正确:
code: int
class Point:
coords: Tuple[int, int]
label: str = '<unknown>'
# 错误:
code:int # 冒号后没有空格
code : int # 冒号前有空格
class Test:
result: int=0 # 赋值号周围没有空格。
脚注
悬挂缩进是一种排版风格,该风格中段落除第一行以外的所有行都缩进。在Python的上下文中,这个术语用于描述这样一种样式,即被括号括住的语句的开始括号是行的最后一个非空白字符,随后直到结束括号的行都被缩进。 |
参考文献
Barry的GNU Mailman风格指南http://barry.warsaw.us/software/STYLEGUIDE.txt |
Donald Knuth的The TeXBook,195页和196页 |
Typeshed repo GitHub - python/typeshed: Collection of library stubs for Python, with static types |
版权声明
本文档已归属于公共领域。