前言

PEP 8-Python代码样式指南。

操作系统:Windows 11 家庭中文版

参考文档

  1. PEP 8 – Style Guide for Python Code

介绍

本文档给出了组成Python主发行版中标准库的Python代码的编码约定。

愚蠢的一致性是小头脑的妖精

Guido的关键见解之一是代码的阅读频率远高于编写频率。这里提供的指南旨在提高代码的易读性,并使其在各种Python代码中保持一致。正如PEP 20所说,“可读性很重要”。

风格指南是关于一致性的。与此风格指南的一致性很重要。项目内的一致性更重要。一个模块或功能内的一致性是最重要的。

但是,知道什么时候不一致——有时风格指南建议不适用。有疑问时,用你最好的判断。看看其他例子,决定什么看起来最好。不要犹豫,问!

特别是:不要为了遵守这个PEP而破坏向后兼容性!

忽略特定指南的其他一些充分理由:

  1. 应用该指南会使代码的可读性降低,即使对于习惯于阅读遵循此PEP的代码的人也是如此。
  2. 与周围的代码保持一致,这些代码也会破坏它(可能是出于历史原因)——尽管这也是一个清理别人烂摊子的机会(以真正的XP风格)。
  3. 因为有问题的代码早于指南的引入,并且没有其他理由修改该代码。
  4. 当代码需要与不支持样式指南推荐的功能的旧版本Python保持兼容时。

代码布局

缩进

每个缩进级别使用4个空格。

Continuation lines should align wrapped elements either vertically using Python’s implicit line joining inside parentheses, brackets and braces, or using a hanging indent. When using a hanging indent the following should be considered; there should be no arguments on the first line and further indentation should be used to clearly distinguish itself as a continuation line:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Correct:

# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)

# Add 4 spaces (an extra level of indentation) to distinguish arguments from the rest.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)

# Hanging indents should add a level.
foo = long_function_name(
var_one, var_two,
var_three, var_four)

1
2
3
4
5
6
7
8
9
10
11
# Wrong:

# Arguments on first line forbidden when not using vertical alignment.
foo = long_function_name(var_one, var_two,
var_three, var_four)

# Further indentation required as indentation is not distinguishable.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)

4空格规则对于连续行是可选的。

Optional:

1
2
3
4
# Hanging indents *may* be indented to other than 4 spaces.
foo = long_function_name(
var_one, var_two,
var_three, var_four)

当if语句的条件部分足够长,需要跨多行写入时,值得注意的是,两个字符关键字(即if)加上单个空格加上左括号的组合会为多行条件的后续行创建一个自然的4空格缩进。这可能会与嵌套在if语句中的缩进代码套件产生视觉冲突,后者也会自然缩进到4个空格。此PEP对于如何(或是否)进一步直观地将此类条件行与if语句中的嵌套套件区分开来没有明确的立场。在这种情况下,可接受的选项包括但不限于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# No extra indentation.
if (this_is_one_thing and
that_is_another_thing):
do_something()

# Add a comment, which will provide some distinction in editors
# supporting syntax highlighting.
if (this_is_one_thing and
that_is_another_thing):
# Since both conditions are true, we can frobnicate.
do_something()

# Add some extra indentation on the conditional continuation line.
if (this_is_one_thing
and that_is_another_thing):
do_something()

(另请参阅下面关于是在二元运算符之前还是之后中断的讨论。)

The closing brace/bracket/parenthesis on multiline constructs may either line up under the first non-whitespace character of the last line of list, as in:

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',
)

制表符还是空格?

空格是首选的缩进方法。

制表符应仅用于与已使用制表符缩进的代码保持一致。

Python不允许混合制表符和空格进行缩进。

Maximum Line Length

将所有行限制为最多79个字符。

对于结构限制较少(文档字符串或注释)的流动长文本块,行长应限制为72个字符。

限制所需的编辑器窗口宽度可以让多个文件并排打开,并且在使用在相邻列中显示两个版本的代码审查工具时效果很好。

大多数工具中的默认换行会破坏代码的视觉结构,使其更难理解。选择这些限制是为了避免在窗口宽度设置为80的编辑器中换行,即使该工具在换行时在最后一列中放置了标记字形。一些基于Web的工具可能根本不提供动态换行。

一些团队非常喜欢更长的行长。对于专门或主要由能够就此问题达成一致的团队维护的代码,可以将行长限制增加到99个字符,前提是注释和文档字符串仍然以72个字符包装。

Python标准库是保守的,要求将行限制为79个字符(文档字符串/注释限制为72个)。

The preferred way of wrapping long lines is by using Python’s implied line continuation inside parentheses, brackets and braces. Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation.

反斜杠有时可能仍然合适。例如,长的、多个with语句在Python 3.10之前不能使用隐式延续,因此在这种情况下可以接受反斜杠:

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())

(有关此类多行with语句缩进的进一步想法,请参阅之前关于多行if语句的讨论。)

另一种这样的情况是assert语句。

确保适当地缩进继续行。

应该在二元运算符之前还是之后换行?

几十年来,推荐的风格是在二进制运算符之后中断。但这可能会在两个方面损害易读性:运算符往往分散在屏幕上的不同列中,每个运算符都被从其操作数移开,移到前一行。在这里,眼睛必须做额外的工作来判断哪些项目被添加,哪些项目被减去:

1
2
3
4
5
6
7
# Wrong:
# operators sit far away from their operands
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)

为了解决这个易读性问题,数学家及其出版商遵循相反的惯例。唐纳德·克努特在他的《计算机与排版》系列中解释了传统规则:“虽然一段中的公式总是在二进制运算和关系之后中断,但显示的公式总是在二进制运算之前中断”。

遵循数学的传统通常会产生更具可读性的代码:

1
2
3
4
5
6
7
# Correct:
# easy to match operators with operands
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字符作为数据,请避免使用嘈杂的Unicode字符,如z̯̯͡a̧͎̺l̡͓̫g̹̲o̡̼̘ 和字节顺序标记。

Python标准库中的所有标识符都必须使用仅限ASCII的标识符,并且应该在可行的情况下使用英文单词(在许多情况下,使用非英文的缩写和技术术语)。

鼓励具有全球受众的开源项目采用类似的政策。

Imports

  • 导入通常应在单独的行上:
1
2
3
# Correct:
import os
import sys

1
2
# Wrong:
import sys, os

不过可以这么说:

1
2
# Correct:
from subprocess import Popen, PIPE
  • 导入总是放在文件的顶部,就在任何模块注释和文档字符串之后,在模块全局变量和常量之前。

导入应按以下顺序分组:

  1. 标准库导入。
  2. 相关第三方进口。
  3. 本地应用程序/库特定的导入。

您应该在每组导入之间放置一个空行。

  • 建议使用绝对导入,因为如果导入系统配置不正确(例如包中的目录最终位于sys.path上),它们通常更具可读性并且往往表现更好(或至少提供更好的错误消息):
1
2
3
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

但是,显式相对导入是绝对导入的可接受替代方案,尤其是在处理复杂的包布局时,使用绝对导入会不必要地冗长:

1
2
from . import sibling
from .sibling import example

标准库代码应避免复杂的包布局并始终使用绝对导入。

  • 从包含类的模块导入类时,通常可以拼写为:
1
2
from myclass import MyClass
from foo.bar.yourclass import YourClass

如果此拼写导致本地名称冲突,请显式拼写它们:

1
2
import myclass
import foo.bar.yourclass

使用 myclass.MyClass 和 foo.bar.yourclass.YourClass.

  • 应该避免通配符导入(from <module> import *),因为它们会使命名空间中存在哪些名称变得不清楚,从而混淆读者和许多自动化工具。通配符导入有一个合理的用例,即将内部接口作为公共API的一部分重新发布(例如,用可选加速器模块的定义覆盖接口的纯Python实现,并且事先不知道哪些定义将被覆盖)。

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

模块级Dunder名称

模块级别的“Dunders”(即带有两个前导和两个尾随下划线的名称),如__all__、author、__version__等。应该放在模块文档字符串之后,但放在任何导入语句之前,除了__future__导入。Python要求未来导入必须出现在模块中除文档字符串之外的任何其他代码之前:

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

String Quotes

在Python中,单引号字符串和双引号字符串是一样的。这个PEP对此不做推荐。选择一个规则并坚持下去。但是,当一个字符串包含单引号或双引号字符时,使用另一个来避免字符串中的反斜杠。它提高了易读性。

对于三引号字符串,始终使用双引号字符以与PEP 257中的文档字符串约定保持一致。

表达式和语句中的空格

Pet Peeves

在以下情况下避免无关的空格:

  • 紧接在括号、括号或大括号内:
1
2
# Correct:
spam(ham[1], {eggs: 2})

1
2
# Wrong:
spam( ham[ 1 ], { eggs: 2 } )
  • 在尾随逗号和后面的右括号之间:
1
2
# Correct:
foo = (0,)

1
2
# Wrong:
bar = (0, )
  • 紧接在逗号、分号或冒号之前:
1
2
# Correct:
if x == 4: print(x, y); x, y = y, x

1
2
# Wrong:
if x == 4 : print(x , y) ; x , y = y , x
  • 然而,在切片中,冒号的作用类似于二元运算符,并且应该在两边具有相等的数量(将其视为具有最低优先级的运算符)。在扩展切片中,两个冒号必须应用相同数量的行间距。例外:当省略切片参数时,空格将被省略:
1
2
3
4
5
6
# 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]

1
2
3
4
5
# Wrong:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : step]
ham[ : upper]
  • 紧接在开始函数调用的参数列表的左括号之前:
1
2
# Correct:
spam(1)

1
2
# Wrong:
spam (1)
  • 紧接在开始索引或切片的左括号之前:
1
2
# Correct:
dct['key'] = lst[index]

1
2
# Wrong:
dct ['key'] = lst [index]
  • 赋值(或其他)运算符周围的多个空格以将其与另一个空格对齐:
1
2
3
4
# Correct:
x = 1
y = 2
long_variable = 3

1
2
3
4
# Wrong:
x = 1
y = 2
long_variable = 3

其他建议

  • 避免在任何地方尾随空格。因为它通常是不可见的,所以可能会令人困惑:例如,反斜杠后跟空格和换行符不算作行延续标记。一些编辑器不保留它,许多项目(如CPython本身)有拒绝它的预提交挂钩。

  • 始终在两侧用单个空格包围这些二元运算符:赋值(=)、增量赋值 (+=, -= 等)、比较 (==, <, >, !=, <>, <=, >=, in、not in、is not)、布尔值(and、or、not)。

  • 如果使用不同优先级的运算符,请考虑在优先级最低的运算符周围添加空格。使用您自己的判断;但是,永远不要使用多个空格,并且在二元运算符的两边始终有相同数量的空格:

1
2
3
4
5
6
# Correct:
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
# Wrong:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)
  • 函数注释应使用冒号的正常规则,如果存在,则始终在->箭头周围有空格。(有关函数注释的更多信息,请参阅下面的函数注释。):
1
2
3
# Correct:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...

1
2
3
# Wrong:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
  • 当用于指示关键字参数或用于指示未注释函数参数的默认值时,不要在=符号周围使用空格:
1
2
3
# Correct:
def complex(real, imag=0.0):
return magic(r=real, i=imag)

1
2
3
# Wrong:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)

但是,当将参数注释与默认值结合使用时,请在=符号周围使用空格:

1
2
3
# Correct:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...

1
2
3
# Wrong:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
  • 通常不鼓励复合语句(同一行上的多个语句):
1
2
3
4
5
6
# Correct:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()

而不是:

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

何时使用尾随逗号

尾随逗号通常是可选的,除非在制作一个元素的元组时它们是强制性的。为清楚起见,建议将后者括在(技术上多余的)括号中:

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

1
2
# Wrong:
FILES = 'setup.cfg',

当尾随逗号是多余的时,当使用版本管理系统时,当值、参数或导入项的列表预计会随着时间的推移而扩展时,它们通常很有帮助。这种模式是将每个值(等)单独放在一行上,总是添加尾随逗号,并在下一行添加右括号/括号/大括号。然而,在与结束分隔符相同的行上有尾随逗号是没有意义的(除了上面的单例元组):

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

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

注释

与代码相矛盾的注释比没有注释更糟糕。当代码更改时,始终优先保持注释最新!

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

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

在多句注释中,除了在最后一句话之后,你应该在句子结束后使用一个或两个空格。

确保你的注释清晰,并且容易被你所写语言的其他使用者理解。

来自非英语国家的Python编码员:请用英语写下您的评论,除非您120%确定不会说您的语言的人永远不会阅读该代码。

Block Comments

块注释通常应用于它们后面的一些(或全部)代码,并缩进到与该代码相同的级别。块注释的每一行都以#和单个空格开头(除非注释中的文本缩进)。

块注释中的段落由包含单个#的行分隔。

Inline Comments

谨慎使用内联注释。

内联注释是与语句在同一行上的注释。内联注释应与语句至少用两个空格分隔。它们应该以#和单个空格开头。

内联注释是不必要的,如果它们陈述了显而易见的内容,实际上会分散注意力。不要这样做:

1
x = x + 1                 # Increment x

但有时,这很有用:

1
x = x + 1                 # Compensate for border

Documentation Strings

编写好的留档字符串(又名“文档字符串”)的约定在PEP 257中永垂不朽。

  • 为所有公共模块、函数、类和方法编写文档字符串。非公共方法不需要文档字符串,但您应该有一个注释来描述该方法的作用。此注释应出现在def行之后。
  • PEP 257描述了良好的文档字符串约定。请注意,最重要的是,结束多行文档字符串的"""应该单独在一行上:
1
2
3
4
"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""
  • 对于一行字符串,请将结尾"""保持在同一行:
1
"""Return an ex-parrot."""

命名约定

Python库的命名约定有点混乱,所以我们永远不会完全一致——尽管如此,这里是目前推荐的命名标准。新的模块和包(包括第三方框架)应该按照这些标准编写,但是如果现有库有不同的风格,内部一致性是首选。

Overriding Principle

作为API的公共部分对用户可见的名称应遵循反映用法而不是实现的约定。

描述性:命名样式

有许多不同的命名风格。它有助于识别正在使用的命名风格,独立于它们的用途。

通常区分以下命名样式:

  • b (single lowercase letter)
  • B (single uppercase letter)
  • lowercase
  • lower_case_with_underscores
  • UPPERCASE
  • UPPER_CASE_WITH_UNDERSCORES
  • CapitalizedWords (or CapWords, or CamelCase – so named because of the bumpy look of its letters). This is also sometimes known as StudlyCaps.

Note: When using acronyms in CapWords, capitalize all the letters of the acronym. Thus HTTPServerError is better than HttpServerError.

  • mixedCase (differs from CapitalizedWords by initial lowercase character!)
  • Capitalized_Words_With_Underscores (ugly!)

还有一种使用短唯一前缀将相关名称组合在一起的风格。这在Python中使用不多,但为了完整性而提到。例如,os.stat()函数返回一个元组,其项目传统上具有st_mode、st_size、st_mtime等名称。(这样做是为了强调与POSIX系统调用结构字段的对应关系,这有助于程序员熟悉这一点。)

X11库对其所有公共函数使用前导X。在Python中,这种样式通常被认为是不必要的,因为属性和方法名称以对象为前缀,函数名称以模块名称为前缀。

此外,以下使用前导或尾随下划线的特殊形式被识别(这些通常可以与任何案例约定相结合):

  • _single_leading_underscore: weak “internal use” indicator. E.g. from M import * does not import objects whose names start with an underscore.
  • single_trailing_underscore_: used by convention to avoid conflicts with Python keyword, e.g. :
1
tkinter.Toplevel(master, class_='ClassName')
  • __double_leading_underscore: when naming a class attribute, invokes name mangling (inside class FooBar, __boo becomes _FooBar__boo; see below).
  • __double_leading_and_trailing_underscore__: “magic” objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented.

规范性:命名约定

要避免的名称

切勿使用字符“l”(小写字母el)、“O”(大写字母oh)或“I”(大写字母eye)作为单字符变量名称。

在某些字体中,这些字符与数字1和0无法区分。当想使用“l”时,请改用“L”。

ASCII兼容性

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

包和模块名称

模块应该有简短的全小写名称。如果可以提高易读性,可以在模块名称中使用下划线。Python包也应该有简短的全小写名称,尽管不鼓励使用下划线。

当用C或C++编写的扩展模块附带提供更高级别(例如更面向对象)接口的Python模块时,C/C++模块具有前导下划线(例如_socket)。

类名称

类名通常应使用CapWords约定。

在接口被记录并主要用作可调用对象的情况下,可以使用函数的命名约定。

请注意,内置名称有一个单独的约定:大多数内置名称是单个单词(或两个单词一起运行),CapWords约定仅用于异常名称和内置常量。

类型变量名

PEP 484中引入的类型变量的名称通常应该使用更喜欢短名称的CapWords:T、AnyStr、Num。建议在用于声明协变或逆变行为的变量中添加后缀_co或_contra:

1
2
3
4
from typing import TypeVar

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

异常名称

因为异常应该是类,所以类命名约定在这里适用。但是,您应该在异常名称上使用后缀“错误”(如果异常实际上是错误)。

全局变量名

(让我们希望这些变量仅用于一个模块内。)约定与函数的约定大致相同。

设计用于from M import*的模块应该使用__all__机制来防止导出全局变量,或者使用旧的约定,在这些全局变量前加上下划线(您可能希望这样做以指示这些全局变量是“模块非公共的”)。

函数和变量名

函数名应该是小写的,根据需要用下划线分隔单词,以提高易读性。

变量名遵循与函数名相同的约定。

为了保持向后兼容性,仅在已经是流行样式的上下文(例如threading.py)中允许使用mixedCase。

功能和方法参数

始终使用self作为实例方法的第一个参数。

始终将cls用于类方法的第一个参数。

如果函数参数的名称与保留关键字冲突,通常最好附加单个尾随下划线,而不是使用缩写或拼写损坏。因此class_比clss更好。(也许更好的方法是使用同义词来避免这种冲突。)

方法名称和实例变量

使用函数命名规则:必要时小写,单词用下划线分隔,以提高易读性。

仅对非公共方法和实例变量使用一个前导下划线。

为避免与子类发生名称冲突,请使用两个前导下划线来调用Python的名称修饰规则。

Python将这些名称与类名混为一谈:如果类Foo有一个名为__a的属性,则Foo无法访问它。__a。(坚持的用户仍然可以通过调用Foo._Foo__a来获得访问权限。)通常,双前导下划线只能用于避免与设计为子类的类中的属性发生名称冲突。

注意:关于__names的使用有一些争议(见下文)。

Constants

常量通常在模块级别上定义,并用所有大写字母书写,下划线分隔单词。示例包括MAX_OVERFLOW和TOTAL。

为继承而设计

始终决定一个类的方法和实例变量(统称为“属性”)应该是公共的还是非公共的。如果有疑问,请选择非公共;稍后将其公开比将公共属性非公开更容易。

公共属性是您希望类中不相关的客户端使用的属性,并承诺避免向后不兼容的更改。非公共属性是那些不打算被第三方使用的属性;您不能保证非公共属性不会更改甚至被删除。

我们在这里不使用术语“私有”,因为在Python中没有任何属性是真正私有的(通常没有不必要的工作量)。

另一类属性是那些属于“子类应用编程接口”(在其他语言中通常称为“受保护的”)的属性。一些类被设计为继承自,以扩展或修改类行为的各个方面。在设计这样的类时,要注意明确决定哪些属性是公共的,哪些是子类应用编程接口的一部分,哪些是真正只供基类使用的。

考虑到这一点,以下是Pythonic指南:

  • 公共属性不应有前导下划线。

  • 如果您的公共属性名称与保留关键字冲突,请在属性名称后附加单个尾随下划线。这比缩写或损坏的拼写更可取。(然而,尽管有此规则,“cls”是已知为类的任何变量或参数的首选拼写,尤其是类方法的第一个参数。)

注意1:有关类方法,请参阅上面的参数名称推荐。

  • 对于简单的公共数据属性,最好只公开属性名称,而不使用复杂的访问器/修改器方法。请记住,如果您发现简单的数据属性需要增长功能行为,Python为未来的增强提供了一条简单的途径。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法后面。

注意1:尽量保持功能行为没有副作用,尽管缓存等副作用通常没问题。

注意2:避免将属性用于计算昂贵的操作;属性表示法使调用者相信访问(相对)便宜。

  • 如果你的类打算被子类化,并且你有不想让子类使用的属性,考虑用双前导下划线命名它们,不要尾随下划线。这会调用Python的名称修饰算法,在该算法中,类的名称被修饰成属性名称。如果子类无意中包含具有相同名称的属性,这有助于避免属性名称冲突。

注意1:请注意,在损坏的名称中只使用了简单的类名,因此如果子类同时选择相同的类名和属性名,您仍然可以获得名称冲突。

注2:名称修改可能会使某些用途不太方便,例如调试和__getattr__()。然而,名称修改算法有据可查,易于手动执行。

注意3:不是每个人都喜欢名字修饰。尽量平衡避免意外名字冲突的需要和高级呼叫者的潜在用途。

公共和内部接口

任何向后兼容性保证仅适用于公共接口。因此,用户能够清楚地区分公共接口和内部接口非常重要。

文档化的接口被认为是公共的,除非留档明确声明它们是临时接口或内部接口,不受通常的向后兼容性保证的约束。所有未文档化的接口都应该被假定为内部接口。

为了更好地支持自省,模块应该使用__all__属性在其公共API中显式声明名称。将__all__设置为空列表表示模块没有公共API。

即使__all__设置得当,内部接口(包、模块、类、函数、属性或其他名称)仍然应该以单个前导下划线为前缀。

如果任何包含的命名空间(包、模块或类)被视为内部的,则接口也被视为内部的。

导入的名称应始终被视为实现细节。其他模块不得依赖对此类导入名称的间接访问,除非它们是包含模块API的明确记录的一部分,例如os.path或公开子模块功能的包的__init__模块。

编程建议

  • 代码的编写方式不应不利于Python的其他实现(PyPy、Jython、IronPython、Cython、Philco等)。

例如,对于形式为a+=b或a=a+b的语句,不要依赖CPython的就地字符串连接的有效实现。这种优化即使在CPython中也是脆弱的(它只适用于某些类型),并且根本不存在于不使用重新计数的实现中。在库的性能敏感部分,应该使用''.join()形式。这将确保连接在各种实现中以线性时间发生。

  • Comparisons to singletons like None should always be done with is or is not, never the equality operators.

Also, beware of writing if x when you really mean if x is not None – e.g. when testing whether a variable or argument that defaults to None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context!

  • Use is not operator rather than not … is. While both expressions are functionally identical, the former is more readable and preferred:
1
2
# Correct:
if foo is not None:

1
2
# Wrong:
if not foo is None:
  • When implementing ordering operations with rich comparisons, it is best to implement all six operations (__eq__, __ne__, __lt__, __le__, __gt__, __ge__) rather than relying on other code to only exercise a particular comparison.

To minimize the effort involved, the functools.total_ordering() decorator provides a tool to generate missing comparison methods.

PEP 207 indicates that reflexivity rules are assumed by Python. Thus, the interpreter may swap y > x with x < y, y >= x with x <= y, and may swap the arguments of x == y and x != y. The sort() and min() operations are guaranteed to use the < operator and the max() function uses the > operator. However, it is best to implement all six operations so that confusion doesn’t arise in other contexts.

  • 始终使用def语句而不是将lambda表达式直接绑定到标识符的赋值语句:
1
2
# Correct:
def f(x): return 2*x

1
2
# Wrong:
f = lambda x: 2*x

第一种形式意味着结果函数对象的名称特别是“f”而不是通用的“<lambda>”。这对于一般的回溯和字符串表示更有用。赋值语句的使用消除了lambda表达式相对于显式def语句的唯一好处(即它可以嵌入到更大的表达式中)

  • Derive exceptions from Exception rather than BaseException. Direct inheritance from BaseException is reserved for exceptions where catching them is almost always the wrong thing to do.

Design exception hierarchies based on the distinctions that code catching the exceptions is likely to need, rather than the locations where the exceptions are raised. Aim to answer the question “What went wrong?” programmatically, rather than only stating that “A problem occurred” (see PEP 3151 for an example of this lesson being learned for the builtin exception hierarchy)

Class naming conventions apply here, although you should add the suffix “Error” to your exception classes if the exception is an error. Non-error exceptions that are used for non-local flow control or other forms of signaling need no special suffix.

  • Use exception chaining appropriately. raise X from Y should be used to indicate explicit replacement without losing the original traceback.

When deliberately replacing an inner exception (using raise X from None), ensure that relevant details are transferred to the new exception (such as preserving the attribute name when converting KeyError to AttributeError, or embedding the text of the original exception in the new exception message).

  • When catching exceptions, mention specific exceptions whenever possible instead of using a bare except:
1
2
3
4
try:
import platform_specific_module
except ImportError:
platform_specific_module = None

A bare except: clause will catch SystemExit and KeyboardInterrupt exceptions, making it harder to interrupt a program with Control-C, and can disguise other problems. If you want to catch all exceptions that signal program errors, use except Exception: (bare except is equivalent to except BaseException:).

A good rule of thumb is to limit use of bare ‘except’ clauses to two cases:

  1. If the exception handler will be printing out or logging the traceback; at least the user will be aware that an error has occurred.
  2. If the code needs to do some cleanup work, but then lets the exception propagate upwards with raise. try…finally can be a better way to handle this case.
  • When catching operating system errors, prefer the explicit exception hierarchy introduced in Python 3.3 over introspection of errno values.

  • Additionally, for all try/except clauses, limit the try clause to the absolute minimum amount of code necessary. Again, this avoids masking bugs:

1
2
3
4
5
6
7
# Correct:
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)

1
2
3
4
5
6
7
# Wrong:
try:
# Too broad!
return handle_value(collection[key])
except KeyError:
# Will also catch KeyError raised by handle_value()
return key_not_found(key)
  • When a resource is local to a particular section of code, use a with statement to ensure it is cleaned up promptly and reliably after use. A try/finally statement is also acceptable.

  • 上下文管理器在执行获取和释放资源以外的操作时应通过单独的函数或方法调用:

1
2
3
# Correct:
with conn.begin_transaction():
do_stuff_in_transaction(conn)

1
2
3
# Wrong:
with conn:
do_stuff_in_transaction(conn)

后一个示例没有提供任何信息来指示__enter____exit__方法正在做的事情不是在事务后关闭连接。在这种情况下,显式很重要。

  • 在返回语句中保持一致。函数中的所有返回语句都应该返回表达式,或者它们都不应该返回表达式。如果任何返回语句返回表达式,任何没有返回值的返回语句都应该明确地将其声明为return None,并且应该在函数的末尾出现一个显式返回语句(如果可以访问):
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)
  • Use ‘’.startswith() and ‘’.endswith() instead of string slicing to check for prefixes or suffixes.

startswith() and endswith() are cleaner and less error prone:

1
2
# Correct:
if foo.startswith('bar'):

1
2
# Wrong:
if foo[:3] == 'bar':
  • Object type comparisons should always use isinstance() instead of comparing types directly:
1
2
# Correct:
if isinstance(obj, int):

1
2
# Wrong:
if type(obj) is type(1):
  • 对于序列(字符串、列表、元组),使用空序列为false的事实:
1
2
3
# Correct:
if not seq:
if seq:

1
2
3
# Wrong:
if len(seq):
if not len(seq):
  • 不要编写依赖重要尾随空格的字符串文字。这种尾随空格在视觉上无法区分,一些编辑器(or more recently, reindent.py)会修剪它们。

  • 不要使用==将布尔值与True或False进行比较:

1
2
# Correct:
if greeting:

1
2
# Wrong:
if greeting == True:

更糟:

1
2
# Wrong:
if greeting is True:
  • Use of the flow control statements return/break/continue within the finally suite of a try...finally, where the flow control statement would jump outside the finally suite, is discouraged. This is because such statements will implicitly cancel any active exception that is propagating through the finally suite:
1
2
3
4
5
6
# Wrong:
def foo():
try:
1 / 0
finally:
return 42

函数注释

随着PEP 484的接受,函数注释的样式规则发生了变化。

  • 函数注释应使用PEP 484语法(上一节中有一些注释格式建议)。

  • 不再鼓励之前在本PEP中推荐的注释样式实验。

  • 然而,在stdlib之外,现在鼓励在PEP 484规则内进行实验。例如,使用PEP 484样式类型注释标记大型第三方库或应用程序,查看添加这些注释有多容易,并观察它们的存在是否增加了代码的可理解性。

  • Python标准库在采用此类注释时应该保守,但它们允许用于新代码和大型重构。

  • 对于想要对函数注释进行不同使用的代码,建议对表单进行注释:

1
# type: ignore

靠近文件顶部;这告诉类型检查器忽略所有注释。(可以在PEP 484中找到禁用类型检查器投诉的更细粒度方法。)

  • Like linters, type checkers are optional, separate tools. Python interpreters by default should not issue any messages due to type checking and should not alter their behavior based on annotations.

  • Users who don’t want to use type checkers are free to ignore them. However, it is expected that users of third party library packages may want to run type checkers over those packages. For this purpose PEP 484 recommends the use of stub files: .pyi files that are read by the type checker in preference of the corresponding .py files. Stub files can be distributed with a library, or separately (with the library author’s permission) through the typeshed repo.

变量注释

PEP 526引入了变量注释。对它们的样式建议类似于上面描述的函数注释:

  • 模块级变量、类和实例变量以及局部变量的注释应该在冒号后面有一个空格。
  • 冒号前不应该有空格。
  • 如果赋值有右手边,那么等号两边应该正好有一个空格:
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
  • Although the PEP 526 is accepted for Python 3.6, the variable annotation syntax is the preferred syntax for stub files on all versions of Python (see PEP 484 for details).

结语

第一百六十六篇博文写完,开心!!!!

今天,也是充满希望的一天。