Python 编码规范 PEP 8 文档完整版保存备查

liftword3个月前 (02-18)技术文章22


PEP 8文档的完整中文版:


PEP: 8
标题: Python代码风格指南
作者: Guido van Rossum guido@python.org,
Barry Warsaw barry@python.org,
Alyssa Coghlan ncoghlan@gmail.com
状态: 活跃
类型: 流程
创建时间: 2001年7月5日
历史: 2001年7月5日, 2013年8月1日


介绍

本文档提供了Python标准库中代码的编码规范。请参阅相关的PEP文档,了解Python的C实现中C代码的风格指南。

本文档和PEP 257(文档字符串规范)改编自Guido最初的Python风格指南文章,并加入了Barry风格指南中的一些内容。

随着时间的推移,随着新的规范被识别和旧规范因语言变化而过时,本风格指南也在不断演变。

许多项目都有自己的编码风格指南。在发生冲突时,项目特定的指南应优先于本指南。


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

Guido的一个关键见解是,代码被阅读的次数远多于被编写的次数。本文提供的指南旨在提高代码的可读性,并使其在广泛的Python代码中保持一致。正如PEP 20所说:“可读性很重要”。

风格指南是关于一致性的。与此风格指南保持一致很重要。项目内部的一致性更为重要。模块或函数内部的一致性最为重要。

然而,知道何时不一致——有时风格指南的建议并不适用。如有疑问,请运用你的最佳判断。查看其他示例并决定哪种方式看起来最好。不要犹豫去问!

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

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

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

代码布局

缩进

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

续行应使用Python的隐式行连接(在括号、方括号和大括号内)或使用悬挂缩进。使用悬挂缩进时,应考虑以下事项:第一行不应有参数,并且应使用进一步的缩进来明确区分续行:

# 正确:

# 与开头的分隔符对齐。
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语句的条件部分足够长,需要跨多行编写时,值得注意的是,两个字符的关键字(即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):
    # 由于两个条件都为真,我们可以进行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',
)

制表符还是空格?

空格是首选的缩进方法。

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

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

(有关此类多行with语句缩进的进一步思考,请参阅前面的多行if语句讨论。)

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

确保适当缩进续行。

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

几十年来,推荐的风格是在二元运算符之后换行。但这可能会在两个方面损害可读性:运算符往往会分散在屏幕的不同列上,并且每个运算符都会远离其操作数并移到前一行。在这里,眼睛必须做额外的工作来分辨哪些项目是相加的,哪些是相减的:

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

为了解决这个可读性问题,数学家和他们的出版商遵循相反的惯例。Donald Knuth在他的《计算机与排版》系列中解释了传统规则:“尽管段落中的公式总是在二元操作和关系之后换行,但显示的公式总是在二元操作之前换行。”

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

# 正确:
# 易于将运算符与操作数匹配
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

在Python代码中,允许在二元运算符之前或之后换行,只要在局部保持一致即可。对于新代码,建议使用Knuth的风格。

空行

在顶级函数和类定义周围使用两个空行。

类内部的方法定义周围使用一个空行。

可以(谨慎地)使用额外的空行来分隔相关函数组。在一组相关的一行代码之间可以省略空行(例如一组虚拟实现)。

在函数内部,谨慎使用空行来指示逻辑部分。

Python接受控制-L(即^L)换页符作为空白字符;许多工具将这些字符视为页面分隔符,因此你可以使用它们来分隔文件中相关部分的页面。请注意,一些编辑器和基于Web的代码查看器可能无法将控制-L识别为换页符,并会显示另一个符号。

源文件编码

核心Python发行版中的代码应始终使用UTF-8,并且不应有编码声明。

在标准库中,非UTF-8编码应仅用于测试目的。谨慎使用非ASCII字符,最好仅用于表示地点和人名。如果使用非ASCII字符作为数据,请避免使用嘈杂的Unicode字符,如z???a???l???g??o???和字节顺序标记。

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 import *),因为它们使名称空间中的名称不清晰,使读者和许多自动化工具感到困惑。通配符导入有一个合理的用例,即作为公共API的一部分重新发布内部接口(例如,用可选加速模块的定义覆盖纯Python实现的接口,并且事先不知道哪些定义将被覆盖)。
  • 以这种方式重新发布名称时,以下关于公共和内部接口的指南仍然适用。

模块级别的双下划线名称

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

"""这是示例模块。

这个模块做了一些事情。
"""

from __future__ import barry_as_FLUFL

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

import os
import sys

字符串引号

在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 : : step] 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

其他建议

  • 避免在任何地方使用尾随空白。因为它通常是不可见的,所以可能会引起混淆:例如,反斜杠后跟空格和换行符不算作行继续标记。一些编辑器不会保留它,许多项目(如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()

何时使用尾随逗号

尾随逗号通常是可选的,除非在创建单元素元组时是必需的。为了清晰起见,建议将后者括在(技术上冗余的)括号中:

# 正确:
FILES = ('setup.cfg',)
# 错误:
FILES = 'setup.cfg',

当尾随逗号冗余时,它们在使用版本控制系统时通常很有帮助,当值、参数或导入项的列表预计会随着时间的推移而扩展时。模式是将每个值(等)放在单独的行上,始终添加尾随逗号,并在下一行添加闭括号/方括号/大括号。然而,在同一行上作为闭分隔符的尾随逗号没有意义(除了上述单元素元组的情况):

# 正确:
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描述了良好的文档字符串约定。最重要的是,结束多行文档字符串的"""应单独成行:
  • """返回一个foobang 可选的plotz表示先frobnicate bizbaz。 """
  • 对于单行文档字符串,请将结束的"""保持在同一行:
  • """返回一个前鹦鹉。"""

命名约定

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

首要原则

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

描述性:命名风格

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

以下命名风格通常被区分:

  • b(单个小写字母)
  • B(单个大写字母)
  • lowercase(小写)
  • lower_case_with_underscores(小写加下划线)
  • UPPERCASE(大写)
  • UPPER_CASE_WITH_UNDERSCORES(大写加下划线)
  • CapitalizedWords(或CapWords,或CamelCase——因其字母的凹凸不平而得名)。这也被称为StudlyCaps。
  • 注意:在CapWords中使用首字母缩略词时,将首字母缩略词的所有字母大写。因此,HTTPServerError比HttpServerError更好。
  • mixedCase(与CapitalizedWords的区别在于首字母小写!)
  • 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'(小写字母el)、'O'(大写字母oh)或'I'(大写字母eye)作为单字符变量名。

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

ASCII兼容性

标准库中使用的标识符必须符合PEP 3131中描述的ASCII兼容性。

包和模块名称

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

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

类名

类名通常应使用CapWords约定。

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

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

类型变量名称

在PEP 484中引入的类型变量的名称通常应使用CapWords,优先使用短名称: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__机制来防止导出全局变量,或使用较旧的约定,即在此类全局变量前加上下划线(你可能希望这样做以指示这些全局变量是“模块非公共的”)。

函数和变量名称

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

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

仅在已经是主要风格的上下文中允许使用mixedCase(例如threading.py),以保持向后兼容性。

函数和方法参数

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

始终使用cls作为类方法的第一个参数。

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

方法名称和实例变量

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

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

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

Python使用类名修饰这些名称:如果类Foo有一个名为__a的属性,则不能通过Foo.__a访问它。(坚持的用户仍然可以通过调用Foo._Foo__a来访问它。)通常,双前导下划线应仅用于避免与设计为子类的类中的属性名称冲突。

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

常量

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

设计继承

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

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

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

另一类属性是“子类API”的一部分(在其他语言中通常称为“受保护”)。一些类设计为被继承,以扩展或修改类的行为。在设计此类类时,请小心明确决定哪些属性是公共的,哪些是子类API的一部分,哪些仅由你的基类使用。

考虑到这一点,以下是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、Psyco等)的方式编写。
  • 例如,不要依赖CPython对a += b或a = a + b形式的就地字符串连接的高效实现。即使在CPython中,这种优化也是脆弱的(它仅适用于某些类型),并且在未使用引用计数的实现中根本不存在。在库的性能敏感部分,应使用''.join()形式。这将确保在各种实现中连接以线性时间发生。
  • 与单例(如None)的比较应始终使用is或is not,而不是相等运算符。
  • 此外,当测试变量或参数是否设置为其他值时(例如,当测试默认值为None的变量或参数是否设置为其他值时),请注意编写if x,而你的意思是if x is not None——例如,当测试变量或参数是否设置为其他值时。其他值可能具有在布尔上下文中为假的类型(例如容器)!
  • 使用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表达式相对于显式def语句的唯一优势(即它可以嵌入到更大的表达式中)。
  • 从Exception而不是BaseException派生异常。直接继承BaseException保留给几乎总是错误地捕获它们的异常。
  • 基于代码捕获异常时可能需要的区别设计异常层次结构,而不是基于异常引发的位置。旨在以编程方式回答“出了什么问题?”而不是仅仅陈述“发生了问题”(参见PEP 3151以了解此教训在内置异常层次结构中的应用)。
  • 类命名约定适用于此,尽管如果异常是错误,你应该在异常类上添加后缀“Error”。用于非本地流控制或其他形式信号传递的非错误异常不需要特殊后缀。
  • 适当使用异常链。raise X from Y应用于指示显式替换而不丢失原始回溯。
  • 当故意替换内部异常(使用raise X from None)时,确保将相关细节转移到新异常(例如在将KeyError转换为AttributeError时保留属性名称,或将原始异常的文本嵌入新异常消息中)。
  • 捕获异常时,尽可能提及特定异常,而不是使用裸except:子句:
  • try: import platform_specific_module except ImportError: platform_specific_module = None
  • 裸except:子句将捕获SystemExit和KeyboardInterrupt异常,使得使用Control-C中断程序更加困难,并且可能掩盖其他问题。如果你想捕获所有表示程序错误的异常,请使用except Exception:(裸except等同于except BaseException:)。
  • 一个好的经验法则是将裸'except'子句的使用限制在两种情况下:
  • 如果异常处理程序将打印或记录回溯;至少用户会知道发生了错误。
  • 如果代码需要做一些清理工作,但随后让异常向上传播。在这种情况下,try...finally 可能是更好的处理方式。
  • 当捕获操作系统错误时,优先使用 Python 3.3 引入的显式异常层次结构,而不是通过 errno 值进行内省。
  • 此外,对于所有的 try/except 子句,将 try 子句限制在绝对必要的最小代码量内。这同样可以避免掩盖错误:
  • # 正确: try: value = collection[key] except KeyError: return key_not_found(key) else: return handle_value(value) # 错误: 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 套件传播的异常:
  • # 错误: def foo(): try: 1 / 0 finally: return 42

函数注解

随着 PEP 484 的接受,函数注解的风格规则发生了变化。

  • 函数注解应使用 PEP 484 语法(前面的部分中有一些关于注解格式的建议)。
  • 之前在本 PEP 中推荐的注解风格实验不再鼓励。
  • 然而,在标准库之外,现在鼓励在 PEP 484 规则内进行实验。例如,用 PEP 484 风格的类型注解标记大型第三方库或应用程序,审查添加这些注解的难易程度,并观察它们的存在是否提高了代码的可理解性。
  • Python 标准库在采用此类注解时应保持保守,但允许在新代码和大型重构中使用它们。
  • 对于希望以不同方式使用函数注解的代码,建议在文件顶部附近放置以下形式的注释:
  • # type: ignore
  • 这告诉类型检查器忽略所有注解。(PEP 484 中提供了更细粒度的禁用类型检查器警告的方法。)
  • 像 linter 一样,类型检查器是可选的、独立的工具。Python 解释器默认不应因类型检查而发出任何消息,也不应基于注解更改其行为。
  • 不想使用类型检查器的用户可以自由忽略它们。然而,预计第三方库包的用户可能希望对这些包运行类型检查器。为此,PEP 484 推荐使用存根文件:.pyi 文件,类型检查器优先读取这些文件而不是相应的 .py 文件。存根文件可以与库一起分发,或单独分发(在库作者的许可下)通过 typeshed 仓库。

变量注解

PEP 526 引入了变量注解。它们的风格建议与上述函数注解的建议类似:

  • 模块级变量、类和实例变量以及局部变量的注解应在冒号后使用单个空格。
  • 冒号前不应有空格。
  • 如果赋值有右侧,则等号两侧应恰好有一个空格:
  • # 正确: code: int class Point: coords: Tuple[int, int] label: str = '' # 错误: code:int # 冒号后没有空格 code : int # 冒号前有空格 class Test: result: int=0 # 等号周围没有空格
  • 尽管 PEP 526 被 Python 3.6 接受,但变量注解语法是所有 Python 版本上存根文件的首选语法(有关详细信息,请参阅 PEP 484)。

脚注

  • [#fn-hi] 悬挂缩进 是一种排版风格,其中段落中的所有行都缩进,除了第一行。在 Python 的上下文中,该术语用于描述一种风格,其中括号语句的开括号是行的最后一个非空白字符,后续行缩进直到闭括号。

参考文献

  • [2] Barry 的 GNU Mailman 风格指南
    http://barry.warsaw.us/software/STYLEGUIDE.txt
  • [3] Donald Knuth 的 The TeXBook,第 195 和 196 页。
  • [4] http://www.wikipedia.com/wiki/Camel_case
  • [5] Typeshed 仓库
    https://github.com/python/typeshed

版权

本文件已置于公共领域。


# 使用 flake8 检查代码
flake8 my_script.py


# 使用 black 格式化代码
black my_script.py

持续学习、适应变化、记录点滴、复盘反思、成长进步

相关文章

01.数据 - Python语言的基本常识一 代码风格

希望系统性学习一门编程语言的朋友请关注我,本专栏后续会持续分享优质文章。介绍本篇是第三篇Python教程,在刚入手学习一门编程语言时,大家不用急着上来就去学习它的语法、它的特性、范式、技巧等,这样在...

Python中字符串操作大盘点_python 字符串

1. 字符串的创建在 Python 中,可以使用单引号、双引号、三引号来创建字符串,三引号还可用于创建多行字符串。# 单引号创建字符串 single_quoted_str = 'Hello, Pyth...