PEP8和PEP257中文翻译版

[TOC]

说在前面

这是为我所在的实验室而准备的代码风格指南~

本代码风格指南此版本基本完全基于$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
    8
    my_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
    8
    my_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 长语句不能使用隐式续码,因此在这种情况下可以使用反斜线:

1
2
3
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())

除此之外还有断言语句$assert$。

二元运算符处的换行

在二元运算符之前换行

1
2
3
4
5
6
7
# 错误:
# 操作符离操作数太远了
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
1
2
3
4
5
6
7
# 正确:
# 操作符与操作数很容易匹配起来
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)

空行

  • 用两行空行将顶级函数和类的定义围起来
  • 类内方法定义用一个空行包围
  • 可少量使用额外空行来分割函数组
  • 在函数中使用少量空行指示逻辑部分
  • 总而言之,空行的作用就是使代码结构层次分明

源文件编码

$Python 2$ 默认$ASCII$,$Python 3$ 默认$UTF-8$。

对于$Python3$无需指明代码,对于$Python2$需指定编码格式为$UTF-8$,

需要在文件顶端插入

1
# -*- coding: UTF-8 -*-

或者

1
# coding=utf-8

$import$

  • 应该单行$import$

    1
    2
    3
    # 正确:
    import os
    import sys
    1
    2
    # 错误:
    import sys, os

    但对于from-import则可以单行

    1
    2
    # 正确:
    from subprocess import Popen, PIPE
  • $import$应该放在文件的顶部,在模块注释和$docstring$之后,在模块全局和常量之前

  • $import$应该遵循以下顺序分组

    1. 标准库$import$
    2. 相关第三方库$import$
    3. 本地应用/库$import$

    每组之间应该用一行空格分隔。

  • 推荐使用绝对导入,因为它们通常更具可读性,而且在导入系统配置不正确的情况下(如软件包内的目录最终出现在 sys.path),往往会表现得更好(或至少给出更好的错误信息)

    1
    2
    3
    import mypkg.sibling
    from mypkg import sibling
    from mypkg.sibling import example

    但应该避免复杂的布局并总是使用绝对导入

    当从模块中导入类时,应该

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

    如果这样导致与本地命名冲突,则应该

    1
    2
    import myclass
    import foo.bar.yourclass

    并且使用myclass.MyClassfoo.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$外所有代码之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
"""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

字符串引号

在 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
    7
     Correct:
    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$是必须的。为了清晰起见,建议用括号(从技术上讲是多余的)将后者包围起来:

1
2
3
4
5
# Correct:
FILES = ('setup.cfg',)

# Wrong:
FILES = 'setup.cfg',

如果$Trailing \hspace{0.5em} Commas$是多余的,那么在使用版本控制系统(如$Git$)时,当预期值、参数或导入项的列表会随着时间的推移而扩展时,$Trailing \hspace{0.5em} Commas$通常会很有用。通常的做法是,将每个值(等)单独放在一行,始终加上一个$Trailing \hspace{0.5em} Commas$,然后在下一行加上封闭括号/小括号/大括号。但是,将尾逗号与关闭分隔符放在同一行是不合理的(上述单元组的情况除外):

1
2
3
4
5
6
7
8
9
10
11
12
# Correct:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)

# Wrong:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

注释

与代码相矛盾的注释比没有注释还要糟糕!当代码改变时总是先修改注释!

注释应该是完整的句子,以大写字母开头(除非是标识符)。

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

块注释

块注释通常适用于跟在它们后面的一些(或全部)代码,且与该代码有相同的缩进级别。块注释的每一行都以一个#和一个空格开始(除非它是注释内的缩进文本)。

块注释内的段落之间通过包含单个#的行进行分隔。

行注释

行内注释应该谨慎使用。

行内注释是在与语句相同的行上的注释。行内注释应至少与语句间隔两个空格。它们应以#和一个空格开始。

如果行内注释陈述的是显而易见的事情,那么它们就没有必要,甚至会分散注意力。不要这样:

1
x = x + 1                 # Increment x

但有时候这是有用的:

1
x = x + 1                 # Compensate for border

$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$适用于很简单很显然的情况

1
2
3
4
5
def kos_root():
"""Return the pathname of the KOS root directory."""
global _kos_root
if _kos_root: return _kos_root
...

需要注意:

  • 即使$docstring$只需要一行,也应该使用"""三重双引号""",方便日后拓展;

  • “””对于单行$docstring$,三重双引号的开头和结尾应该在同一行”””

  • 函数/方法的单行$docstring$的前后都没有空行

  • 应用祈使句的形式来规定函数或方法的效果(比如Do this, Return that),而不是使用单纯的描述

  • 单行$docstring$不应该重申函数/方法的参数(可通过内省获得),不要这么做:

    1
    2
    def function(a, b):
    """function(a, b) -> list"""

    这种$docstring$的最佳形式应该是:

    1
    2
    def 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$应该提及这一点,并总结其中的区别,同时正确使用overrideextand

不使用$Emac$命名约定(不使用大写字母来表示函数或方法的参数)。Python是区分大小写的,参数名可以用于关键字参数,所以文档字符串应该记录正确的参数名。在$docstring$中,每个参数最好单独成行。例如:

1
2
3
4
5
6
7
8
9
10
def complex(real=0.0, imag=0.0):
"""Form a complex number.

Keyword arguments:
real -- the real part (default 0.0)
imag -- the imaginary part (default 0.0)
"""
if imag == 0.0 and real == 0.0:
return complex_zero
...

除非整个文档字符串可以放在一行中,否则请将结尾引号单独放在一行中。这样,就可以使用 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_(后缀单下划线):通常用于避免与关键字冲突,如

    1
    tkinter.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-publicpublic会更简单些。

$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 += ba = a + b这样形式的字符串拼接语句的高效实现。这种优化即使在$CPython$中也是很脆弱的(只对某些类型有效),而且不是所有的Python实现都支持引用计数的。对于性能敏感的代码段,采用''.join()形式能保证在不同的Python实现中都可以在线性时间内完成字符串拼接。
  • 与单例比如None的比较,使用isis 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
    4
    try:
    import platform_specific_module
    except ImportError:
    platform_specific_module = None

    空的except:子句将捕获SystemExitKeyboardInterrupt异常,这将使Control-C中断程序变得更加困难,还会掩盖其他问题。如果想捕获所有提示程序错误的异常,使用except Exception(空的except:子句相当于except BaseException:)。

    一个好的经验法则是将空except:子句的使用限制在以下两种情况中:

    1. 如果exception handler将打印或log记录回溯,至少让用户知道错误发生了。
    2. 如果代码需要做一些清理工作,但又要用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

  • 不要使用==将布尔值和TrueFalse比较:

    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 526PEP 484,但$PEP \hspace{0.5em} 484$应是首选语法。

  • 模块级变量、类和实例变量以及局部变量的注解应在冒号后留一个空格,冒号前不需要空格。

  • 如果右边有赋值,那么类型注解的两边各有一个空格

    1
    2
    3
    4
    5
    6
    7
    8
    # 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