运维咖啡吧

享受技术带来的乐趣,体验生活给予的感动

PEP 8 -- Python 代码风格指南

PEP 8 -- Python 代码风格指南

1、引言

本文档给出了 Python 编码规约,主要 Python 发行版中的标准库即遵守该规约。对于 C 代码风格的 Python 程序,请参阅配套的 C 代码风格指南。

本文档和 PEP 257(文档字符串规约)是根据 Guido 的 Python 风格指南一文改编的,其中还增加了 Barry 提出的一些风格指南。

风格指南并非一成不变,它本身也在不断发展,过去的惯例会因语言本身的变化而过时。

许多项目有自己的编码风格,在发生冲突时,这类项目的风格指南应优先考虑。

2、固执己见必成心灵之魔

Guido 认为:代码被阅读之频繁远甚于其被编写。所以,这里提供的准则旨在提高代码的可读性并使其在各种 Python 代码中保持一致。如 PEP 20 所述,“可读性至关重要”。

风格指南是有关一致性的重要规约。书写与此风格指南一致的代码很重要,书写与项目风格一致的代码更加重要,在一个模块或函数内, 书写风格一致的代码超级重要。

所以,风格指南并不适用于所有情况,要知道什么时候不一致,这需要你运用自己的智慧进行判断。多看一些例子,选择你认为最好的那个,必要的时候要懂得向别人请教。

尤其注意:不要为了满足风格指南而破坏与过去代码风格的兼容性!

以下情况可以不必考虑风格指南:

  1. 即使对于习惯此风格代码的人,应用此风格指南也会使代码的可读性降低。
  2. 为了与上下文代码风格一致(可能由于历史原因,上下文的风格也违背了本指南),当然这也是一个规范代码风格的机会(通过改变上下文的风格)。
  3. 有些代码早于此风格指南出现并且没有其他理由要修改这些代码。
  4. 代码需要与旧版本的 Python 代码兼容,但旧版本不支持此风格指南。

3、代码布局

(1)缩进

每级缩进对应四个空格。

()、[]或者{}可以隐式的换行,三种括号所包裹的代码要么垂直对齐,要么悬挂缩进。当使用悬挂缩进时,应该注意:参数不能放在首行,续行要再缩进一级以便和后边的代码区别开。

# 正确写法:   

# 与左括号对齐(垂直对齐写法)
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)
# 错误写法:   

# 参数放在第一行而没有垂直对齐
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语句中的代码块产生冲突。本文档不提供确切的方法来解决条件行和 if 语句的嵌套代码块的冲突。以下是一些可行的处理方法,但不必局限于此:   

# 无额外的缩进
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# 通过注释进行区分
if (this_is_one_thing and
    that_is_another_thing):
    # Since both conditions are true, we can frobnicate.
    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',
)

(2)Tab还是空格?

推荐使用空格控制缩进。

tab 只用于与之前的代码保持一致的情况。

Python3 不允许混合使用 tab 和空格控制缩进。

Python2 中混合使用 tab 和空格缩进的代码应改成只使用空格缩进。

当使用 -t 选项调用 Python2 命令行解释器时,会给出混合使用 tab 和空格的警告。使用 -tt 选项时,这些警告会变为错误。我们推荐使用这些选项。

(3)单行最大长度

每行最多 79 个字符。

对于具有较少结构限制的长文本块(如文档字符串和注释)每行最多 72 个字符。

限制编辑器的窗口宽度可以在屏幕上并排打开多个文件,同时也能更好地使用代码审阅工具(在相邻两列中显示两个版本的代码)。

大多数工具默认的换行方式破坏了代码可见的结构,让代码变得难于理解。在窗口大小为 80 的编辑器中,即使换行的代码会在最后一列被打上标记,为了避免自动换行也需要限制每行字符长度。一些基于 Web 的工具可能根本不会自动换行。

有些团队会喜欢很长的代码行。如果代码只由或主要由他们维护并且也在内部达成一致的话,将代码行的长度增加到 80 到 100 个字符(实际上最大行长是 99 个字符)都没有什么问题。当然,注释和文档字符串仍然维持在72个字符以内。

Python 标准库比较保守,选择将代码行最大长度限制为 79 个字符,注释或者文档字符串限制为 72 个字符。

Python 中首选的换行方式是使用括号括起来隐式地换行。使用括号可以将长的代码行分成多个较短行。这种写法要优先于使用 \ 换行。

在某些情况下,也可以使用 \ 换行,比如,当 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 语句。

要确保续行的缩进适当。

(4)在二元运算符之前还是之后换行?

长期以来,一直推荐的风格是在二元运算符之后换行,但是这会影响代码可读性,一是会使运算符分散在屏幕的不同列上,二是会使每个运算符留在前一行,并远离操作数分离。必须人为地判断应该加上或者减去哪些东西,这就增加了人眼的负担。

# 错误写法:   

# 运算符远离操作数
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

为了解决这一可读性问题,数学家和出版商遵循了相反的规约。Donald Knuth 在他的《Computers and Typesetting》丛书中解释了这一规约。“虽然段落中的公式总是在二元运算符后换行,但显示公式时总是在二元运算符之前换行。”

遵循数学上的传统可以写出可读性更好的代码。

# 正确写法:   

# 更容易匹配运算符与操作数
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

在 Python 代码中,只要前后保持一致,在二元操作符之前或之后换行都可以。对于新写的代码,建议使用 Knuth 推荐的风格。

(5)空行

顶级函数和类的定义前后空两行。

类内部的方法定义前后空一行。

可以使用额外的空行(尽量少)来分隔相关函数组。一系列相关的仅占一行的函数之间的空白行可以省略(比如一系列虚函数)。

在函数内部可以使用空行(尽量少)来分割逻辑上的代码块。

Python 将 Ctrl + L 换页符作为空格,但许多工具将它当作分页符,所以你可以用这种方法把文件中的相关代码放在一页。注意:一些编辑器和基于 Web 的代码阅读器可能不会把 Ctrl + L 当作换页符处理,并且在该位置会显示其他的字符。

(6)源文件编码

当前核心的 Python 发行版本一直使用 utf-8 编码,在 Python2 中则使用 ASCII 编码。

Python2 使用 ASCII ,Python3 使用 utf-8 编码,这在文件中不需要进行编码声明。

在标准库中,不使用非默认编码,除非出于测试目的。当注释或文档字符串中使用的作者名包含非 ASCII 字符,也可以使用非默认编码。其他情况下,若要在字符串中包含非 ASCII 数据,建议使用转义字符 \x \u \U \N

对 Python3 及更高版本的标准库有以下规定(详见 PEP 3131):Python 标准库中的所有标识符均为 ASCII 编码并且尽可能使用英文单词。某些情况也可以使用非英文的缩写和术语。另外,字符串字面量和注释也必须使用 ASCII 字符。以下情况例外:
测试非 ASCII 字符的特性
作者姓名。作者姓名如果不在拉丁字母表中(latin-1, ISO/IEC 8859-1 字符集),必须给出基于此字母表的音译名。

我们鼓励全球受众的开源项目采用类似规约。

(7)导入

# 正确写法:
import os
import sys
# 错误写法:
import sys, os

这种写法也可以:

from subprocess import Popen, PIPE

注意分组导入,并遵循一下顺序:

  1. 标准库导入
  2. 相关的第三方包导入
  3. 本地应用或库的特定导入

不同的组应使用空行分隔

import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

然而,清晰的相对路径导入方式也可以接受,特别是当包的结构比较复杂,使用绝对路径导入方式没有必要,反而会使代码显得冗杂。

from . import sibling
from .sibling import example

标准库的代码没有复杂的包结构,并且一直使用绝对路径的导入方式。

不能使用隐式的相对路径导入,这在 Python3 中已经被移除。

from myclass import MyClass
from foo.bar.yourclass import YourClass

如果这种写法导致和本地命名空间冲突,则改为:

import myclass
import foo.bar.yourclass

然后使用myclass.MyClass 或者foo.bar.yourclass.YourClass

当使用这种方式重新发布名称时,指南后面关于公共和内部接口的部分仍然适用。

(8)模块级双下划线名称

模块级的双下划线名称(即在名称的开始和结尾处都有两条下划线)如__all__,authorversion 等,应该写在文档字符串之后,除from __future__外其他导入语句之前。Python 要求在模块中future-imports语句要放在除文档字符串外的任何其他代码之前。例如:

"""This is the example module.

This module does stuff.
"""

from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

import os
import sys

4、字符串引用

在 Python 中,单引号字符串和双引号字符串是一样的。本 PEP 文档在这一点上不做要求,选用一种并坚持下去就好。当字符串本身包含单引号或者双引号时,那就选用未包含的引号来表示字符串,这样可以避免使用\,代码也更易读。

如果使用三引号形式的字符串,则用双引号"组成三引号""",这样可以和 PEP 257 中的文档字符串规约保持一致。

5、表达式和语句中的空格

(1)一些痛点

以下情形中要避免使用过多空格:

# 正确写法:
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

(2)其他建议

# 正确写法:
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)
# 正确写法:
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 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()

6、何时在末尾加逗号

末尾的逗号通常是可选的。但是,在定义单元素元组时是必须的(而且在 Python2 中,逗号对 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,)

7、注释

和代码相矛盾的注释还不如没有注释。当代码更新时,要优先更改注释,使其保持最新状态。

注释应该是完整的句子。第一个单词首字母要大写,除非是一个小写字母开头的标识符(永远不要修改标识符的大小写!)。

块注释通常由完整句子构成的一个或多个段落组成,每个句子都以句号结束。

在多语句注释中,除了最后一条句子,应当在句尾的句号后面加两个空格。

如果使用英文写作,参考 Strunk 和 White 的著作。

来自非英语国家的 Python 程序员们:请书写英文注释,除非你 120% 确定你所写的代码永远不会被不懂你所用语言的人读到。

(1)块注释

块注释一般写在对应代码之前,并且和对应代码有同样的缩进级别。块注释以一个 # 和一个空格打头(除非注释内的文本也有缩进)。

块注释中的段落用以单个 # 字符开头的空行分隔。

(2)行内注释

少用行内注释。

行内注释和代码语句写在同一行,至少以两个空格分隔。并且也以一个 # 和一个空格打头。

行内注释通常不是必要的,在代码含义很明显时甚至会让人分心。不要有以下写法:

x = x + 1                 # Increment x

但有时却是必要的:

x = x + 1                 # Compensate for border

(3)文档字符串

要写出好的文档字符串(又名“docstrings”),请参阅 PEP 257 。

"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""

8、命名规约

Python 库的命名规约有些混乱,所以我们无法就此保持完全一致。但我们当前还是有一些值得推荐的命名规约。书写新的模块和包(包括第三方框架)时,应当遵循这些标准。但是如果现有的库遵循了不同的代码风格,那么应该保持内部代码的一致性。

(1)首要原则

对用户可见的公共部分 API ,其名称应该反应出其用途而不是实现。

(2)描述:命名风格

不同的命名风格有很多,最好能从应用他们的代码而识别出对应命名风格。

注意区别以下命名风格:

注意:当使用首字母缩略词时,将缩略词的所有字母大写。因此HTTPServerError的写法比HttpServerError更好。

还有的命名风格用简短的唯一前缀将相关的名称写在一组。这在 Python 中不常用,但完整起见,这里点出来。例如,os.stat()函数会返回一个元组,其中包含st_modest_sizest_mtime等名称。这样做是为了强调和 POSIX 系统调用结构之间的关系,让程序员更熟悉。

X11 库中的公共的函数名都以 X 开头。在 Python 中,一般认为这种风格没什么必要,因为属性和方法名都以对象名为前缀,函数名以模块名为前缀。

另外,以下用下划线开始或结尾的特殊形式也是被认可的(与其他规约结合使用):

Tkinter.Toplevel(master, class_='ClassName')

(3)规范:命名规约

避免的命名

不要使用小写l(el),大写O(oh)或者大写I(eye)作为单字符变量名。某些字体中,这些字符和数字0、1无法区分。如果想用l,可以用L代替。

ASCII 兼容性

如 PEP 3131 policy 部分所述,标准库中的标识符必须与 ASCII 兼容。

包和模块命名

模块名要简短并且全部小写。如果能提高可读性,也可以在模块名中加下划线。Python 包名称也要简短和小写,但不鼓励使用下划线。当使用 C 或 C++ 写的扩展模块附带 Python 模块,并且该模块提供更高级的接口(比如,更具面向对象特性)时,则 C/C++ 模块名以下划线开头(例如,_sociket)。

类命名

类命名应使用驼峰命名法。

当接口已有文档说明且主要是被用作调用时,也可以使用函数的命名规约。注意,内建名称有独立的命名规约:大部分内建名称是一个单词(或者组合使用的两个单词),驼峰命名法只适用于异常名和内建常量。

类型变量命名

类型变量名在 PEP 484 中引入,一般使用驼峰命名法,且要尽量简短:T AnyStr Num。对应用到协变和逆变行为的相应变量名,建议添加后缀_co_contra。例如:

from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)

异常命名

因为异常也是类,所以类命名规约也使用于异常。但是,如果异常实际上是抛出错误时,那么异常名后应该加上"Error"。

全局变量命名

(我们希望这些变量只能用于一个模块中。)这些规约和函数命名规约大致相同。

对于使用方式设计成from M import *的模块,应该使用__all__机制来避免导入全局变量。或者采用在全局变量前加下划线的旧规约,来说明这些变量是模块级非公共的。

函数和变量名

函数名应该小写,必要时可使用下划线分隔单词来提高可读性。

变量名和函数名遵循相同的规约。

只有当已有代码风格已经是混合大小写时(比如 threading.py),为了保留向后兼容性才使用混合大小写。

函数和方法参数

实例方法的第一参数永远都是self

类方法的第一个参数永远都是cls

当函数的参数名称与保留关键字冲突时,相比使用缩写或拼写简化,使用以下划线结尾的名称更好。所以class_clss更好。(或许更好的方法是通过使用同义词来避免这种冲突。)

方法命名和实例变量

使用函数命名规约:单词小写,必要时以下划线分隔。

非公共方法和实例变量以下划线打头。

为避免和子类产生命名冲突,使用双下划线打头的方式来调用 Python 的命名修饰机制。

Python 将这种名称和类名混合到一起使用:如果类Foo有一个属性叫__a,则不能使用Foo.__a的方式访问。(如果用户坚持想访问,可以使用Foo._Foo__a。)一般来说,使用双下划线打头仅仅是为了避免与可继承类的属性发生命名冲突。

注意:关于双下划线开头的命名方式还有争议。

常量

常量通常是在模块级别定义的,并且全部采用大写字母,单词之间以下划线分隔。例如:TOTAL MAX_OVERFLOW

继承的设计

永远记得区别类方法和实例变量(属性)应该是公开的还是非公开的。如果有所疑虑,就选择非公开。因为,将非公开属性变成公开属性和容易,反之很难。

类中的公开属性是留给与此类无关的客户使用的,并且保证向后的兼容性。非公开属性是那些不打算让第三方使用的部分,这类属性可能被更改甚至移除。

这里,我们不使用“私有的”这一术语,因为在 Python 中没有真正的私有属性(避免了大量不必要的工作)。

另一类属性是子类 API (在其他语言中经常被称为“受保护的”)的一部分。有些类是为继承而设计的,要么会扩展要么会修改类的行为。当设计这样的类时,注意,一定要明确的说明哪些属性是公开的,哪些是子类 API,哪些是真正只被基类调用的。

考虑到这些,得出以下 Python 风格指南:

注意 1:对于类方法,参考之前的参数命名建议。

注意 1:properties注解仅对新式类起作用。

注意 2:尽量消除功能行为的副作用,尽管缓存之类的副作用没有什么坏处。

注意 3:避免对计算量大的操作使用properties注解,属性注解会让调用者认为开销相对较低。

注意 1:命名修饰仅仅是简单的用类名修饰了属性名,如果你在子类中同时使用类名+属性名,也会遇到命名冲突问题。

注意 2:命名修饰可以有特定的用途,比如调试和__getattr__(),只是不够方便。然而,命名修饰算法已经有很好的文档化了,手动执行也很容易。

注意 3:不是所有人都喜欢命名修饰,要尽力在避免命名冲突和方便调用之间寻求一种平衡。

(4)公开的和内部的接口

任何向后兼容性保证仅适用于公开接口。因此,用户必须能够明确区分公开和内部接口。一般认为文档化的接口是公开的,除非文档明确说明他们是临时的或者是内部的接口,那就不保证向后兼容性。所有的非文档化接口应当被认为是内部的。

为了更好的审视公开接口和内部接口,模块应该在__all__属性中明确声明哪些是公开的 API。将__all__设置为空列表以表明模块中没有公开 API。

即使正确设置了__all__属性,内部接口(包,模块,类,函数,属性或者其他名称)也应该使用一条下划线打头。

在任何内部的命名空间(包、模块或类)中的接口也被认为是内部的。

应始终将导入的名称视为底层实现。其他模块不能依赖对这些导入名称的间接访问,除非在包含的模块中已经被明确地文档化了,比如os.path或者包的__init__模块,他们使用子模块中的公开功能。

9、编程建议

例如,不要依赖 CPython 对字符串拼接的高效实现(即a += b或者a = a + b这样的语句)。即使在 CPython 中这种优化方式也很脆弱,它仅对某些类型起作用,甚至在不使用引用计数方法的实现中都不存在。库中性能敏感的部分应该用''.join()替代。这可以保证,在各种不同的实现中,拼接操作都是线性时间的。

另外,不要把if x is not None写成if x。比如,你想测试默认为 None 的变量或参数是否设置为其他值时,其他值可能是一种特殊类型(比如容器),这种类型在做布尔运算时被当作 false。

# 推荐写法:
if foo is not None:
# 不推荐写法:
if not foo is None:

为最大限度的降低工作量,functools.total_ordering()装饰器提供了一个工具来生成缺少的比较方法。

PEP 207 指出 Python 实现了反射机制。因此,解释器可能使用y > x替换x < y,使用y >= x替换x <= y,也可能交换x == yx != y的操作数。sort()min()操作使用了<运算符,max()函数使用了>运算符。但是,最好是六个操作符都实现,以免在其他情况下出现混淆。

# 推荐写法:
def f(x): return 2*x
# 不推荐写法:
f = lambda x: 2*x

第一种格式意味着生成的函数对象的名称是f而不是通用的<lambda>。通常这对异常追踪和字符串表述是更有用的。使用赋值语句消除了 lambda 表达式唯一优于显式 def 语句的地方,即 lambda 表达式可以嵌入到一个长语句中。

基于代码异常捕获的差异来设计异常继承的层次结构,而不是异常抛出的位置。编程的时候要以回答“出了什么问题?”为目标,而不是仅仅指出“这里出现了问题”。(可以参阅 PEP 3151 中的例子,学习内建异常层级结构的构建经验。)

类命名规约适用于异常,但是,当异常是一种错误的话,要加上“Error”后缀。用于非本地流程控制或者其他形式的信号的非错误类异常,不需要特殊的后缀。

当有意替换一个内部异常时(Python 中使用“raise X”,Python3.3+ 中使用“raise X from None”),要确保将相关细节转移到新异常中(比如,将KeyError转化成AttributeError时保留属性名称,或者将原始异常的文本嵌入到新的异常信息中)。

第二种格式在 Python3 中是不合法的语法。

带括号的形式也意味着当异常参数很长或者包含格式化字符串时,你不需要使用续行符,这多亏了括号。

try:
  import platform_specific_module
except ImportError:
  platform_specific_module = None

一个空的except:子句将会捕获到 SystemExit和 KeyboardInterrupt 异常,这样就很难使用 Ctrl + C 来中断程序,还会掩盖其他问题。如果你想捕获可以表示程序错误的所有异常,可以使用except Exception:(空except:等同于except BaseException:)。

经验告诉我们,在以下两种情况中要限制空except:子句的使用:

  1. 异常处理程序想要打印或者记录回溯信息,至少这能使用户意识到有错误发生。
  2. 如果代码需要做一些清理工作,但是随后要用raise向上抛出异常。那么try...finally可以更好的处理这个问题。

  3. 当要给异常绑定一个名称时,最好使用 Python 2.6 中加入的明确的名称绑定语法:

try:
  process_data()
except Exception as exc:
  raise DataProcessingFailedError(str(exc))

这是 Python3 中唯一支持的语法,并且避免了与基于逗号的旧式语法产生二义性问题。

# 推荐写法:
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 conn.begin_transaction():
  do_stuff_in_transaction(conn)
# 不推荐写法
with conn:
  do_stuff_in_transaction(conn)

第二个例子没有提供任何信息来表明:除了在事物处理之后关闭连接,__enter__ 和__exit__ 方法会做其他事情。在这种情况下,明确是很重要的。

# 推荐写法:
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。如果需要兼容 Python2.0 以下的版本,就需要覆盖此规则。

startswith() 和 endswith() 更简洁,且不容易出错,例如:

# 推荐写法:
if foo.startswith('bar'):
# 不推荐写法:
if foo[:3] == 'bar':
# 推荐写法:
if isinstance(obj, int):
# 不推荐写法:
if type(obj) is type(1):

当检查一个对象是否为字符串时,注意它也可能是一个 Unicode 字符串。在 Python2 中,str 和 Unicode 有一个公共的基类即 basestring,所以你可以这样做:

if isinstance(obj, basestring):

注意,在 Python3 中,unicodebasestring已经不存在了(只有str)。并且bytes对象不再是一种字符串(反而是一种整数序列)。

# 推荐写法:
if not seq:
if seq:
# 不推荐写法:
if len(seq):
if not len(seq):
# 推荐写法:
if greeting:
# 不推荐写法
if greeting == True:
# 错误写法:
if greeting is True:

(1)函数注解

随着 PEP 484 被正式接受,函数注解的样式规则正在发生变化。

# type: ignore

这会告诉类型检查器忽略所有注解。(在 PEP 484 中可以找到更细致的方法减少类型检查器报错。)

(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

脚注

悬挂缩进是一种排版样式,除第一行外,其段落中的所有行都要缩进。在 Python 文本中,这一术语描述了这样一种样式:对于括号括起来的语句,该行的最后一个非空白字符是左括号,后续行都要缩进,直到右括号。

10、参考文档

编号 内容
[1] PEP 7,Style Guide for C Code, van Rossum
[2] Barry's GNU Mailman style guide http://barry.warsaw.us/software/STYLEGUIDE.txt
[3] Donald Knuth's The TeXBook, pages 195 and 196.
[4] http://www.wikipedia.com/wiki/CamelCase
[5] Typeshed repo https://github.com/python/typeshed
[6] Suggested syntax for Python 2.7 and straddling code https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code

11、版权

来源:https://peps.python.org/pep-0008/