Python编码规范(PEP8)
Python编码规范(PEP8)
Written by xiaoQQya on 2020/08/26.
PEP
是Python Enhancement Proposal
的缩写,翻译过来就是Python
增强建议书。
一.代码布局
1.1 缩进
每个缩进层次为4个空格。
连续行包装元素的两种方案:
- 隐式续行:续行与圆括号、方括号或花括号的左括号对齐;
- 悬挂缩进:首行没有参数,续行应该多缩进一级以便和正常的缩进区分。
# 隐式续行
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个空格以外的其他空格
foo = long_function_name(
var_one, var_two,
var_three, var_four)
1.2 Tab 或 空格
-
空格是首选的缩进方法。
-
唯一的一种情况下建议使用Tab:之前的代码已经使用了Tab,为了保持一致性。
注:
Python3
不允许混合使用Tab键和空格进行缩进。
1.3 单行最大长度
-
单行最大长度不超过79个字符。
-
为了使较长的文本块具有较少的结构限制(文档字符串或者注释),单行最大长度应该限制为72个字符。
-
换行的首选方法是使用
Python
的隐含续行机制,即在小括号、中括号、大括号内部自动续行。很长的行可以通过把表达式包含在圆括号内,实现自动续行,相比使用反斜杠\续行,这个方法应该更为推荐。 -
有时候反斜杠仍然是适合的。例如,with语句不能使用隐式续行,因此可以接受反斜杠。
1.4 二元操作符换行
在二元操作符之前和之后换行两种方式都是被允许的,但是从可读性的角度,建议在二元操作符之前换行。
-
运算符趋向于分散在屏幕上的不同列上,并且每个运算符都从其操作数移至上一行。但是,这降低了代码的可读性:
# 二元操作符之后换行 income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
-
遵循数学的传统通常会导致代码更具可读性:
# 二元操作符之前换行 income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
注:在
Python
代码中,只要约定在本地是一致的,就可以在二元操作符之前或者之后换行。
1.5 空行
- 顶层方法或者类定义使用两个空行;
- 类中的方法定义使用单个空行;
- 可以使用额外的空行(尽量少)来分隔一组相关的函数;
- 在一些列相关的仅占一行的函数之间,空行可以被省略(比如一组虚函数定义);
- 在方法内部,使用空行来表示逻辑区域(尽量少)。
1.6 源文件编码
-
Python
核心发行版中的源代码应该一直使用utf-8
编码(Python2
中使用ASCII
编码)。 -
使用
ASCII
编码(Python2
)或者utf-8
编码(Python3
)的文件不应该添加编码声明。
1.7 模块引入
-
导入通常应该在单独的行上,但是从单个模块中导入多个部分是可以写在一行上:
# 正确的写法 import os import sys # 错误的写法 import os, sys # 单个模块导入多个部分 from subprocess import Popen, PIPE
-
导入模块应该放在文件顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前。
-
导入应按以下顺序分组:
- 标准库导入;
- 相关的第三方库导入;
- 本地库或者方法导入。
注:在每组导入之间应该放置一个空行。
-
推荐使用绝对导入,因为如果导入系统配置不正确,则它们通常更具可读性,并且通常表现更好。
# 绝对导入 import mypkg.sibling from mypkg import sibling from mypkg.sibling import example
-
显示相对导入是绝对导入的一种可接受的替代方法,尤其是在处理复杂的包装布局时,绝对导入显得冗余的情况下。
# 显示相对导入 from . import sibling from .sibling import example
-
隐式相对导入应该禁止使用,在
Python3
中已经被去掉了。# 隐式相对导入 import sibling
-
从包含类的模块中导入类。
# 一般情况 from myclass import MyClass from foo.bar.yourclass import YourClass # 如果和本地名称冲突,则应该明确拼写它们 import myclass import foo.bar.yourclass
-
应该避免使用通配符导入。
# 通配符导入 from module import *
1.8 模块级双下划线命名
模块级别双下划线变量比如__all__
,__author__
应该放在模块文档字符串之后,但是需要放在import
语句之前。存在一个例外,from __future__ import
应该放在双下划线变量之前,Python
要求模块中__future__
的导入必须出现在除文档字符串之外的任何其他代码之前。代码如下所示:
# 文档字符串,放在最前面
"""
This is the example module.
This module does stuff.
"""
# 特殊情况,__future__导入必须放在除了文档字符串之后的所有代码前面
from __future__ import barry_as_FLUFL
# 双下划线变量,必须放在import之前
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'
# import 语句
import os
import sys
二.字符串引号
在Python
中表示字符串时,不管是单引号还是双引号都是一样的。但是不推荐将这两种方式看作一样并且混用,最好选择一种规则并坚持使用。当字符串中包含单引号时,采用双引号来表示字符串,反之也一样,这样可以避免使用反斜杠,代码也更易读。
对于三引号标识的字符串,使用双引号字符串来表示,这样可以和PEP 257的文档字符串规则保持一致。
三.表达式和语句中的空格
3.1 需要注意的地方
在下列情形中避免使用过多的空白:
-
方括号、圆括号和花括号之后。
# 正确的例子 spam(ham[1], {eggs: 2}) # 错误的例子 spam( ham[ 1 ], { eggs: 2 } )
-
逗号、分号和冒号之前。
# 正确的例子 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
3.2 其他建议
-
避免任何行末的空格。因为它通常是不可见的,它可能会令人困惑:例如反斜杠后跟空格和换行符不会作为续行标记。一些编辑器会自动去除行末空格,许多项目(如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 complex(real, imag=0.0): return magic(r=real, i=imag) # 错误的例子 def complex(real, imag = 0.0): return magic(r = real, i = imag)
-
函数注解中的
:
也遵循一般的:
加空格的规则,在->
两侧各使用一个空格。(参见[函数注解](####7.1 函数注解))# 正确的例子 def munge(input: AnyStr): ... def munge() -> AnyStr: ... # 错误的例子 def munge(input:AnyStr): ... def munge()->PosInt: ...
-
在组合使用函数注解和参数默认值时,需要在
=
两侧各使用一个空格(只有当这个参数既有函数注解,又有默认值的时候)。# 正确的例子 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()
四.何时使用行尾逗号
-
末尾逗号通常是可选的,除非在定义单元素元组
tuple
时是必需的(而且在Python 2
中,它们具有print
语句的语义)。为了清楚起见,建议使用括号(技术上来说是冗余的)括起来。# 正确的例子 FILES = ('setup.cfg',) # 也正确,但令人困惑 FILES = 'setup.cfg',
-
当使用版本控制系统时,在将来有可能扩展的列表末尾添加冗余的逗号是有好处的。具体的做法是将每一个元素写在单独的一行,并在行尾添加逗号,右括号单独占一行。但是,与有括号在同一行的末尾元素后面加逗号是没有意义的(上述的单元素元组除外)。
# 正确的例子 FILES = [ 'setup.cfg', 'tox.ini', ] initialize( FILES, error=True, ) # 错误的例子 FILES = ['setup.cfg', 'tox.ini',] initialize(FILES, error=True,)
五.注释
和代码矛盾的注释还不如没有。当代码有改动时,一定要优先更改注释使其保持最新。
注释应该是完整的多个句子。如果注释是一个短语或一个句子,其首字母应该大写,除非开头是一个以小写字母开头的标识符(永远不要更改标识符的大小写)。
如果注释很短,结束的句号可以被忽略。块注释通常由一段或几段完整的句子组成,每个句子都应该以句号结束。
在多句注释中,除了最后一句之外,你应该在句尾的句号后再加上2个空格。
5.1 块注释
块注释一般写在对应代码之前,并且和对应代码有同样的缩进级别。块注释的每一行都应该以#
和一个空格开头(除非该文本是在注释内缩进对齐的)。
块注释中的段落应该用只含有单个#
的一行隔开。
5.2 行内注释
尽量少用行内注释。
行内注释是和代码语句写在一行内的注释。行内注释应该至少和代码语句之间有两个空格的间隔,并且以#
和一个空格开始。
行内注释通常不是必要的,在代码含义很明显时甚至会让人分心。
请不要这样写:
x = x + 1 # x自加
但是正确的注释会有很大帮助:
x = x + 1 # 边界补偿(注:多用于起点为1的列表,字典等的索引之类的情况)
5.3 文档字符串
要知道如何写出好的文档字符串,请参考PEP 257。
-
所有的公共模块、函数、类和方法都应该有文档字符串。对于非公共方法,文档字符串不是必要的,但你应该留有注释说明该方法的功能,该注释应当出现在
def
的下一行。 -
PEP 257描述了好的文档字符串应该遵循的规则。其中最重要的是,多行文档字符串以单行
"""
结尾,不能有其他字符,例如:"""Return a foobang Optional plotz says to frobnicate the bizbaz first. """
-
对于仅有一行的文档字符串,结尾处的
"""
应该也写在这一行。
六.命名规范
Python
标准库的命名规范有一些混乱,因此我们永远都无法保持一致。但如今仍然存在一些推荐的命名标准。新的模块和包(包括第三方框架)都应该采用这些标准,但已经存在的库如果之前采用其他风格,更推荐保持代码内部的一致性。
6.1 首要原则
对于用户可见的公共部分API,其命名应当表达出功能用途而不是其具体的实现细节。
6.2 描述:命名风格
有很多种不同的命名风格。如果能够认出使用的是什么命名风格将会很有帮助,这和名字被用来做什么是独立的。
通常区分以下命名样式:
-
b
(单个小写字母) -
B
(单个大写字母) -
lowercase
(小写字母) -
lower_case_with_underscores
(带下划线的小写字母) -
UPPERCASE
(大写字母) -
UPPER_CASE_WITH_UNDERSCORES
(带下划线的大写字母) -
CapitalizedWords
(也叫做CapWords
或者CamelCase
– 因为单词首字母大写看起来很像驼峰)注意:当
CapWords
里包含缩写时,将缩写部分的字母都大写。HTTPServerError
比HttpServerError
要好。 -
mixedCase
(和CapitalizedWords
不同在于其首字母小写) -
Capitalized_Words_With_Underscores
(带下划线的驼峰,比较丑)
也有风格使用简短唯一的前缀来表示一组相关的命名。这在Python
中并不常见,但为了完整起见这里也捎带提一下。比如,os.stat()
函数返回一个tuple
,其中的元素名原本为st_mode
,st_size
,st_mtime
等等。(这样做是为了强调和POSIX
系统调用结构之间的关系,可以让程序员更熟悉。)
X11
库中的公共函数名都以X
开头。在Python
中这样的风格一般被认为是不必要的,因为属性和方法名之前已经有了对象名的前缀,而函数名前也有了模块名的前缀。
此外,要区别以下划线开始或结尾的特殊形式(可以和其它的规则结合起来):
-
_single_leading_underscore
: 以单个下划线开头是“内部使用”的弱标志。 比如,from M import *
不会import
下划线开头的对象。 -
single_trailing_underscore_
:以单个下划线结尾用来避免和Python关键词产生冲突,例如:Tkinter.Toplevel(master, class_="ClassName")
__double_leading_underscore
:以双下划线开头的风格命名类属性表示触发命名修饰(在FooBar
类中,__boo
命名会被修饰成_FooBar__boo
)。__double_leading_and_trailing_underscore__
:以双下划线开头和结尾的命名风格表示“魔术”对象或属性,存在于用户控制的命名空间(user-controlled namespaces)里(也就是说,这些命名已经存在,但通常需要用户覆写以实现用户所需要的功能)。 比如,__init__
,__import__
或__file__
。请依照文档描述来使用这些命名,千万不要自己发明。
6.3 规范:命名规范
6.3.1 需要避免的命名
不要使用符号l
(L
的小写字母),O
(o
的大写字母),I
(i
的大写字母)作为单字符的变量名。
在一些字体中,这些字体很难与0 或1进行区分。比如,当尝试使用字符l
时,改成使用L
提醒替代。
6.3.2 ASCII兼容性
标准库中使用的标识符必须与ASCII兼容(参见PEP 3131中的Policy这一节)。
6.3.3 包和模块命名
模块命名应短小,且为全小写,若下划线能提高可读性,也可以在模块名中使用。Python包命名也应该短小,且为全小写,但不应使用下划线。
当使用C/C++
写的扩展模块有相应的Python
模块提供更高级的接口时(比如,更加面向对象),C/C++
模块名应该以下划线开头(例如,_sociket
)。
6.3.4 类命名
类命名应该使用驼峰(CapWords)的命名约定。
当接口已有文档说明且主要是被用作调用时,也可以使用函数的命名约定。
注意对于内建命名(builtin names)有一个特殊的约定:大部分内建名都是一个单词(或者两个一起使用的单词),驼峰(CapWords)的约定只对异常命名和内建常量使用。
6.3.5 类型变量命名
PEP 484中引入的类型变量名称通常应使用简短的驼峰命名: T
,AnyStr
,Num
。建议将后缀_co
或_contra
添加到用于声明相应的协变(covariant)和逆变(contravariant)的行为。例如:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True')
KT_contra = TypeVar('KT_contra', contravariant=True)
6.3.6 异常命名
因为异常是类,类的命名规范可以应用在这里。但是,你应该在异常名上使用Error
后缀(如果这个异常确实是一个Error
)。
6.3.7 全局变量命名
(在此之前,我们先假定这些变量都仅在同一个模块内使用。)这些规范同样也适用于函数命名。
对于引用方式设计为from M import *
的模块,应该使用__all__
机制来避免import
全局变量,或者采用下划线前缀的旧规范来命名全局变量,从而表明这些变量是“模块非公开的”。
6.3.8 函数和变量命名
函数命名应该都是小写,必要时使用下划线来提高可读性。
变量命名和函数命名相同的规则。
只有当已有代码风格已经是混合大小写时(比如threading.py
),为了保留向后兼容性才使用混合大小写。
6.3.9 函数和方法参数
实例方法的第一参数永远都是self
。
类方法的第一个参数永远都是cls
。
在函数参数名和保留关键字冲突时,相对于使用缩写或拼写简化,使用以下划线结尾的命名一般更好。比如,class_
比class
更好。(或许使用同义词避免这样的冲突是更好的方式。)
6.3.10 方法命名和实例变量
使用函数命名的规则:小写单词,必要时使用下划线分开以提高可读性。
仅对于非公开方法和变量命名在开头使用一个下划线。
避免和子类的命名冲突,使用两个下划线开头来触发Python
的命名修饰机制。
Python
类名的命名修饰规则:如果类Foo
有一个属性叫__a
,不能使用Foo.__a
的方式访问该变量(在类的内部可以通过self.__a
的方式去访问,在外部有用户可能仍然坚持使用Foo._Foo__a
的方法访问)。一般来说,两个下划线开头的命名方法仅用于避免与设计为子类的类中的属性名冲突。
注意:关于
__names
的使用也有一些争论。
6.3.11 常量
常量通常是在模块级别定义的,使用全部大写并用下划线将单词分开。如:MAX_OVERFLOW
和TOTAL
。
6.3.12 继承的设计
记得永远区别类的方法和实例变量(属性)应该是公开的还是非公开的。如果有疑虑的话,请选择非公开的;因为之后将非公开属性变为公开属性要容易些。
公开属性是那些你希望和你定义的类无关的客户来使用的,并且确保不会出现向后不兼容的问题。非公开属性是那些不希望被第三方使用的部分,你可以不用保证非公开属性不会变化或被删除。
我们在这里没有使用“私有private
”这个词,因为在Python
里没有什么属性是真正私有的(这样设计省略了大量不必要的工作)。
另一类属性属于子类API的一部分(在其他语言中经常被称为protected
)。一些类是为继承设计的,要么扩展要么修改类的部分行为。当设计这样的类时,需要谨慎明确地决定哪些属性是公开的,哪些属于子类API,哪些只会被你的基类调用。
请记住以上几点,下面是Python
规范的指南:
-
公开属性不应该有开头下划线。
-
如果公开属性的名字和保留关键字有冲突,在你的属性名尾部加上一个下划线。这比采用缩写和简写更好。(然而,和这条规则冲突的是,
cls
对任何变量和参数来说都是一个更好地拼写,因为大家都知道这表示class
,特别是在类方法的第一个参数里。)注意:对于类方法,参考之前的函数和方法参数命名建议。
-
对于简单的公共数据属性,最后仅公开属性名字,不要公开复杂的调用或设值方法。请记住,如果你发现一个简单的数据属性需要增加功能行为时,
Python
为功能增强提供了一个简单的途径。这种情况下,使用Property
注解将功能实现隐藏在简单数据属性访问语法之后。注意 1:
Property
注解仅仅对新规范类有用。注意 2:尽量保证功能行为没有副作用,尽管缓存这种副作用看上去并没有什么大问题。
注意 3:对计算量大的运算避免使用
property
;属性的注解会让调用者相信访问的运算量是相对较小的。 -
如果你的类将被子类继承的话,你有一些属性并不想让子类访问,考虑将他们命名为两个下划线开头并且结尾处没有下划线。这样会触发
Python
命名修饰算法,类名会被修饰添加到属性名中。这样可以避免属性命名冲突,以免子类会不经意间包含相同的命名。注意 1:注意命名修饰仅仅是简单地将类名加入到修饰名中,所以如果子类有相同的类名和属性名,你可能仍然会遇到命名冲突问题。
注意 2:命名修饰可能导致某些用途不太方便,比如调试和
__getattr__()
。然而命名修饰算法可以更好的被记录,并且易于手动执行。注意 3:不是所有人都喜欢命名修饰。需要试着去平衡避免偶然命名冲突的需求和高级调用者使用的潜在可能性。
6.4 公开和内部接口
任何向后兼容性保证仅对公开接口适用。因此,用户能够清楚分辨公开接口和内部接口是很重要的。
文档化的接口被认为是公开的,除非文档中明确声明了它们是临时接口或者内部接口不受通常的向后兼容性保证。所有文档中未提到的接口都应该被认为是内部的。
为了更好审视公开接口和内部接口,模块应该在__all
属性中明确声明公开API是哪些。将__all__
设为空列表表示该模块中没有公开API。
即使正确设置了__all__
属性,内部接口(包,模块,类,函数,属性或其他命名)也应该以单下划线开头。
如果接口的任一一个命名空间(包,模块或类)是内部的,那么该接口也应该是内部的。
导入的命名应该永远被认为是实现细节。其他模块不应当依赖这些非直接访问的导入命名,除非它们在文档中明确地被写为模块的API,例如那些从子模块公开的功能os.path
或者包的__init__
模块。
七.编程建议
-
代码应该以不影响其他
Python
(PyPy
,Jython
,IronPython
,Cython
,Psyco
等)实现的方式编写。例如,不要依赖于
CPython
在字符串拼接时的优化实现,形如这种语句形式a += b
和a = a + b
。即使是CPython
(仅对某些类型起作用) 这种优化也是脆弱的,而且在不使用引用计数的实现中根本不存在优化。在库中性能敏感的部分,用''.join
形式来代替。这会确保在所有不同的实现中字符串拼接是线性时间的。 -
与单例作比较,像
None
应该用is
或is not
,从不使用==
操作符。另外,当你真的要判断
x
是不是None
时当心if x is not None
这样的写法。例如,测试一个默认值为None
的变量或参数是否设置成了其它值,其它值有可能是某种特殊类型(如容器),这种特殊类型在逻辑运算时其值会被当作Flase
来看待。 -
用
is not
操作符而不是not ... is
。虽然这两个表达式是功能相同的,但是前者更易读,是首选。# 正确的例子 if foo is not None # 错误的例子 if not foo is None
-
用大量的比较实现排序操作的时候,最好实现所有的比较操作符(
__eq__
、__ne__
、__lt__
,__le__
,__gt__
,__ge__
),而不是依靠其他代码来进行特定比较。为了最大限度的减少工作量,
functools.total_ordering()
装饰器提供了一个工具去生成缺少的比较方法。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
语句可以提供的唯一好处(即lambda
能镶嵌在一个很长的表达式里)。 -
异常类应派生自
Exception
而不是BaseException
。直接继承BaseException
是为Exception
保留的,从BaseException
继承并捕获异常这种做法几乎总是错的。设计异常的层次结构,应基于那些可能出现异常的代码,而不是引发异常的位置。编码的时候,以回答“出了什么问题?”为目标,而不是仅仅指出“这里出现了问题”(见 PEP 3151 一个内建异常结构层次的例子)。
类的命名规范适用于异常,如果异常类是一个错误,你应该给异常类加一个后缀
Error
。用于非本地流程控制或者其他形式信号的非错误异常不需要一个特殊的后缀。 -
适当的使用异常链。在
Python 3
里,应该使用raise X from Y
来指示显式替换,而不会丢失原始的追溯。当有意替换一个内部的异常时(在
Python 2
用raise X
,Python 3.3+ 用raise X from None
),请确保将相关详细信息转移到新异常中(例如,将KeyError
转换为AttributeError
时保留属性名称,或将原始异常的文本嵌入到新的异常消息中)。 -
捕获异常时,尽可能使用明确的异常,而不是用一个空的
except:
语句。例如:try: import platform_specific_module except ImportError: platform_specific_module = None
一个空的
except:
语句将会捕获到SystemExit
和KeyboardInterrupt
异常,很难区分程序的中断到底是Ctrl+C
还是其他问题引起的。如果你想捕获程序的所有错误,使用except Exception:
(空except:
等同于except BaseException
)。一个好的经验是限制使用空
except
语句,除了这两种情况:- 如果异常处理程序会打印出或者记录回溯信息;至少用户意识到错误的存在。
- 如果代码需要做一些清理工作,但后面用
raise
向上抛出异常。try ... finally
是处理这种情况更好的方式。
-
绑定异常给一个名字时,最好使用
Python 2.6
里添加的明确的名字绑定语法:try: process_data() except Exception as exc: raise DataProcessingFailedError(str(exc))
Python 3
只支持这种语法,避免与基于逗号的旧式语法产生二义性。 -
捕获操作系统异常时,最好使用
Python 3.3
里引进的明确的异常结构层次,而不是内省的errno
值。 -
另外,对于所有
try
/except
子句,将try
子句限制为必需的绝对最小代码量。同样,这样可以避免屏蔽错误。# 正确的例子 try: value = collection[key] except KeyError: return key_not_found(key) else: return handle_value(value) # 错误的例子 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
也是可以接受的。 -
当它们做一些除了获取和释放资源之外的事的时候,上下文管理器应该通过单独的函数或方法调用。例如:
# 正确的例子 with conn.begin_transaction(): do_stuff_in_transaction(conn) # 错误的例子 with conn: do_stuff_in_transaction(conn)
第二个例子没有提供任何信息来表明
__enter__
和__exit__
方法在完成一个事务后做了一些除了关闭连接以外的其它事。在这种情况下明确是很重要的。 -
坚持使用
return
语句。函数内的return
语句都应该返回一个表达式,或者None
。如果一个return
语句返回一个表达式,另一个没有返回值的应该用return None
清晰的说明,并且在一个函数的结尾应该明确使用一个return
语句(如果有返回值的话)。# 正确的例子 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)
-
用字符串方法代替字符串模块。
字符串方法总是快得多,并且与
unicode
字符串共享相同的API。如果需要与2.0以下的Python
向后兼容,则覆盖此规则。 -
用
''.startswith()
和''.endswith()
代替字符串切片来检查前缀和后缀。startswith()
和endswith()
是更简洁的,不容易出错的。例如:# 正确的例子 if foo.startswith('bar'): # 错误的例子 if foo[:3] == 'bar':
-
对象类型的比较应该始终使用
isinstance()
而不是直接比较。# 正确的例子 if isinstance(obj, int): # 错误的例子 if type(obj) is type(1):
当比较一个对象是不是字符串时,记住它有可能也是一个
unicode
字符串!在Python 2
里面,str
和unicode
有一个公共的基类叫basestring
,因此你可以这样做:if isinstance(obj, basestring):
注意:在
Python 3
里面,unicode
和basestring
已经不存在了(只有str
),byte
对象不再是字符串的一种(被一个整数序列替代)。 -
对于序列(字符串、列表、元组)来说,空的序列为
False
。# 正确的例子 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
7.1 函数注解
随着PEP 484被正式接受,函数注释的样式规则已经改变。
-
为了向前兼容,
Python 3
代码中的函数注释最好使用PEP 484语法。 -
建议不再使用在此文档早期版本中描述的试验性质的注解样式。
-
然而,在标准库
stdlib
之外,现在鼓励在PEP 484的规则范围内的实验。例如,使用PEP 484样式类型的注解标记大型第三方库或应用程序,评估添加这些注解的方式是否简便,并观察其存在是否增加了代码可读性。 -
Python
标准库在采用这些注解时应持谨慎态度,但是当编写新代码或进行大的重构时,允许使用。 -
如果希望不按照函数注解的方式来使用函数,可以在文件头部添加以下注释:
# type: ignore
这会告诉类型检查器忽略所有注解。(在PEP 484中可以找到更细致的方式来控制类型检查器的行为。)
-
像代码扫描工具一样
linters
,类型检查器是可选的、单独的工具。默认情况下,Python
解释器不应该由于类型检查而发出任何消息,并且不应该根据注释来改变它们的行为。 -
不想使用类型检查器的用户可以忽略它们。但是,预计第三方库软件包的用户可能希望在这些软件包上运行类型检查器。为此,PEP 484 建议使用存根文件:
.pyi
文件,类型检查器优先于相应的.py
文件读取这个文件。存根文件可以与库一起分发,也可以单独地(在库作者许可的情况下)通过Typeshed repo
分发。 -
对于需要向后兼容的代码,可以以注释的形式添加类型注解。参见PEP 484的相关章节。
7.2 变量注解
PEP 526 引入了变量注解。对于变量注解的推荐样式类似于上面的函数注解:
- 对于模块层的变量,类或实例变量,和局部变量的注解应该在冒号之后有一个空格。
- 在冒号之前应该没有空格。
- 如果赋值有右侧的值,那么等号两侧都应该有一个空格。
# 正确的例子
code: int
class Point:
coords: Tuple[int, int]
label: str = '<unknown'
# 错误的例子
code:int # No space after colon
code : int # Space before colon
class Test:
result: int=0 # No spaces around equality sign
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下