Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 8 – Style Guide for Python Code

Author:
Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Nick Coghlan <ncoghlan at gmail.com>
Status:
Active
Type:
Process
Created:
05-Jul-2001
Post-History:
05-Jul-2001, 01-Aug-2013

Table of Contents

介绍

This document gives coding conventions for the Python code comprising the standard library in the main Python distribution. Please see the companion informational PEP describing style guidelines for the C code in the C implementation of Python.

This document and PEP 257 (Docstring Conventions) were adapted from Guido’s original Python Style Guide essay, with some additions from Barry’s style guide [2].

本风格指南随着时间的推移而发展,因为更多的惯例被确定,而过去的惯例因语言本身的变化而变得过时。

许多项目都有自己的编码风格指南。在有任何冲突的情况下,这种项目特有的指南对该项目来说是优先的。

愚蠢的一致性是小脑袋里的恶棍

One of Guido’s key insights is that code is read much more often than it is written. The guidelines provided here are intended to improve the readability of code and make it consistent across the wide spectrum of Python code. As PEP 20 says, “Readability counts”.

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

然而,要知道什么时候应该不一致 – 有时风格指南的建议就是不适用。当有疑问时,使用你的最佳判断。看看其他的例子,决定什么看起来最好。而且不要犹豫,要问!

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

其他一些忽略某项准则的好理由:

  1. 当应用该准则时,会使代码的可读性降低,甚至对于习惯于阅读遵循该 PEP 的代码的人来说也是如此。
  2. 要与周围的代码保持一致,这些代码也会破坏它(也许是出于历史原因)– 尽管这也是一个清理别人的烂摊子的机会(真正的 XP 风格)。
  3. 因为所涉及的代码是在准则出台之前的,没有其他理由要修改该代码。
  4. 当代码需要与不支持风格指南推荐的特性的旧版本的 Python 保持兼容时。

代码布局

缩进

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

续行应该使用 Python 的小括号、大括号和圆括号内的隐式连线,或者使用 悬空缩进 [1],将包裹的元素垂直对齐。当使用悬空缩进时,应该考虑以下情况;第一行不应该有参数,应该使用进一步的缩进来明确区分自己是一个延续行

# 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)
# 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-空格规则对续行来说是可选的。

可选

# 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 个空格。对于如何(或是否)进一步从视觉上区分这些条件行和 if 语句内的嵌套代码,本 PEP 没有采取明确的立场。这种情况下可接受的选项包括,但不限于

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

(也可参见下文关于在二进制运算符之前或之后断开的讨论)。

多行结构的结尾大括号/方括号/圆括号可以排在最后一行列表的第一个非空格字符下,如

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 的编辑器中出现包装,即使工具在包装行时在最后一列放置一个标记字形。一些基于网络的工具可能根本就不提供动态换行。

有些团队强烈希望有一个较长的行。对于完全或主要由一个团队维护的代码,如果能在这个问题上达成一致,可以将行长限制增加到 99 个字符,但注释和文档字符串仍以 72 个字符包裹。

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

包裹长行的首选方法是在小括号、大括号和大括号内使用 Python 的隐含续行功能。长行可以通过在小括号中包裹表达式而在多行中断开。应该优先使用这些小括号,而不是使用反斜杠进行续行。

Backslashes may still be appropriate at times. For example, long, multiple with-statements could not use implicit continuation before Python 3.10, so backslashes were acceptable for that case:

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 语句。

请确保适当缩进续行。

二元运算符之前或之后应该断行吗?

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

# Wrong:
# operators sit far away from their operands
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

为了解决这个可读性问题,数学家和他们的出版商遵循相反的惯例。Donald Knuth 在他的 计算机和排版 系列中解释了这一传统规则:“虽然一段内的公式总是在二进制运算和关系之后断开,但显示的公式总是在二进制运算之前断开” [3]

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

# Correct:
# easy to match operators with operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

在 Python 代码中,允许在二进制运算符之前或之后断开,只要该约定在本地是一致的。对于新的代码,建议采用 Knuth 的风格。

空白行

用两行空行包围顶级函数和类的定义。

类内的方法定义被一个空行所包围。

可以使用额外的空行(少用)来分隔相关功能组。在一堆相关的单行字之间可以省略空行(例如,一组 dummy 的实现)。

在函数中少用空行,以表示逻辑部分。

Python accepts the control-L (i.e. ^L) form feed character as whitespace; many tools treat these characters as page separators, so you may use them to separate pages of related sections of your file. Note, some editors and web-based code viewers may not recognize control-L as a form feed and will show another glyph in its place.

源文件编码

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

在标准库中,非 UTF-8 编码应该只用于测试目的。少用非 ASCII 字符,最好只用来表示地方和人名。如果使用非 ASCII 字符作为数据,应避免像 z̯̯͡a̧͎̺l̡͓̫g̹̲o̡̼̘ 和字节顺序标记这样的噪音 Unicode 字符。

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

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

Imports

  • 进口通常应在单独的行中进行
    # Correct:
    import os
    import sys
    
    # Wrong:
    import sys, os
    

    虽然这样说也没关系

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

    import 应按以下顺序分组:

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

    你应该在每组 import 之间放一个空行。

  • 推荐使用绝对导入,因为它们通常更具可读性,而且如果导入系统配置不正确(例如当包内的目录最终出现在 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 <module> import *),因为它们使人不清楚哪些名字存在于名字空间中,使读者和许多自动工具都感到困惑。通配符导入有一个合理的用例,那就是重新发布一个内部接口作为公共 API 的一部分(例如,用一个可选的加速器模块的定义覆盖一个纯 Python 接口的实现,究竟哪些定义会被覆盖并不事先知道)。

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

模块级的 Dunder 名称

模块级 的 “dunders”(即有两个前导和两个尾部下划线的名称),如 __all____author____version__,等等。应该放在模块文档串之后,但在任何导入语句之前,除了 from __future__ 导入。Python 规定,future 的导入必须出现在模块中的任何其他代码之前,除了文档字符串

"""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 中,单引号字符串和双引号字符串是一样的。本 PEP 并没有对此提出建议。选择一个规则并坚持下去。然而,当一个字符串包含单引号或双引号字符时,使用另一个,以避免字符串中的反斜线。它可以提高可读性。

For triple-quoted strings, always use double quote characters to be consistent with the docstring convention in PEP 257.

表达式和语句中的空格

Pet Peeves

在以下情况下要避免不相干的空白:

  • 紧挨着小括号、大括号或圆括号的地方
    # Correct:
    spam(ham[1], {eggs: 2})
    
    # Wrong:
    spam( ham[ 1 ], { eggs: 2 } )
    
  • 在尾部的逗号和后面的闭合小括号之间
    # Correct:
    foo = (0,)
    
    # Wrong:
    bar = (0, )
    
  • 紧接在逗号、分号或冒号之前
    # Correct:
    if x == 4: print(x, y); x, y = y, x
    
    # Wrong:
    if x == 4 : print(x , y) ; x , y = y , x
    
  • 然而,在一个切片中,冒号的作用就像一个二元运算符,两边的数量应该相等(把它当作优先级最低的运算符)。在一个扩展的切片中,两个冒号都必须应用相同数量的间距。例外:当一个切片参数被省略时,空格被省略
    # 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 : : upper]
    ham[ : upper]
    
  • 紧挨着开始函数调用的参数列表的开放括号之前
    # Correct:
    spam(1)
    
    # Wrong:
    spam (1)
    
  • 紧接着开始索引或切片的开放括号之前
    # Correct:
    dct['key'] = lst[index]
    
    # Wrong:
    dct ['key'] = lst [index]
    
  • 在一个赋值(或其他)运算符周围有一个以上的空格,以使其与另一个运算符对齐
    # Correct:
    x = 1
    y = 2
    long_variable = 3
    
    # Wrong:
    x             = 1
    y             = 2
    long_variable = 3
    

其他建议

  • 在任何地方都要避免尾部的空白。因为它通常是不可见的,它可能会引起混淆:例如,一个反斜杠后面有一个空格和一个换行符,并不算作一个续行标记。一些编辑器不保留它,许多项目(如 CPython 本身)有预提交钩子,拒绝它。
  • 总是在这些二元运算符的两边用一个空格环绕:赋值(=),增强赋值(+=-=``等),比较(``=`,``<>!=<><=>=innot inisis not),布尔运算(andor`,``not)。
  • 如果使用了不同优先级的运算符,考虑在优先级最低的运算符周围添加空白。使用你自己的判断;然而,永远不要使用一个以上的空格,并且在二元运算符的两边总是有相同数量的空格
    # 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)
    
  • 函数注释应该使用正常的冒号规则,如果存在 -> 箭头,则始终在其周围有空格。(关于函数注释的更多信息,请参见下面的 函数注解
    # Correct:
    def munge(input: AnyStr): ...
    def munge() -> PosInt: ...
    
    # Wrong:
    def munge(input:AnyStr): ...
    def munge()->PosInt: ...
    
  • 当用于表示一个关键字参数时,或用于表示一个 未注解的 函数参数的默认值时,不要在 = 符号周围使用空格
    # 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)
    

    但是,当把参数注解与默认值结合起来时,请在 = 符号周围使用空格

    # 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): ...
    
  • 一般不鼓励使用复合语句(同一行的多个语句)
    # 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 和小的主体放在同一行是可以的,但对于多条款的语句,千万不要这样做。也要避免折叠这样的长行!

    宁可不做

    # Wrong:
    if foo == 'blah': do_blah_thing()
    for x in lst: total += x
    while t < 10: t = delay()
    

    绝对不行

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

何时使用尾部逗号

尾部逗号通常是可选的,但在制作一个元素的元组时是必须的。为了清楚起见,建议用(技术上多余的)小括号包围后者

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

当尾部逗号是多余的,当使用版本控制系统时,当一个值、参数或导入项的列表预计会随着时间的推移而扩展时,尾部逗号往往是有帮助的。其模式是将每个值(等)单独放在一行,总是添加一个尾部逗号,并在下一行添加封闭的小括号/括弧/括号。然而,将尾部逗号与闭合分隔符放在同一行中是没有意义的(除了上述单子图元的情况)

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

注释

与代码相抵触的注释比没有注释更糟糕。当代码发生变化时,一定要优先保持注释的更新!

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

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

在多句注释中,你应该在句子结束的句号后使用两个空格,但最后一句话后除外。

确保你的注释清晰明了,容易被其他使用你所写语言的人理解。

来自非英语国家的 Python 编码员:请用英语写注释,除非你有 120% 的把握,代码永远不会被不讲你的语言的人阅读。

块注释

块注释通常适用于它们后面的一些(或所有)代码,并与该代码缩进到同一水平。块注释的每一行都以 # 和一个空格开始(除非它是注释内的缩进文本)。

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

内联注释

尽量少使用内联注释。

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

内联注释是不必要的,事实上,如果它们说明了明显的问题,就会分散注意力。不要这样做

x = x + 1                 # Increment x

但有时,这是很有用的

x = x + 1                 # Compensate for border

文档字符串

Conventions for writing good documentation strings (a.k.a. “docstrings”) are immortalized in PEP 257.

  • 为所有公共模块、函数、类和方法编写文档字符串。对于非公开的方法,不需要文档字符串,但你应该有一个注释来描述该方法的作用。这个注释应该出现在 def 行之后。
  • PEP 257 describes good docstring conventions. Note that most importantly, the """ that ends a multiline docstring should be on a line by itself:
    """Return a foobang
    
    Optional plotz says to frobnicate the bizbaz first.
    """
    
  • 对于单行的文件串,请将结尾的 """ 放在同一行
    """Return an ex-parrot."""
    

命名公约

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

覆盖原则

作为 API 的公共部分,对用户可见的名称应该遵循 reflect 用法而不是实现的惯例。

描述性的:命名方式

有很多不同的命名方式。能够认识到使用的是什么命名方式,与它们的用途无关,这很有帮助。

常用的命名方式有以下几种:

  • b (单个小写字母)
  • B (单个大写字母)
  • lowercase
  • lower_case_with_underscores
  • UPPERCASE
  • UPPER_CASE_WITH_UNDERSCORES
  • CapitalizedWords (或 CapWords,或 CamelCase – 因其字母 [4] 的凹凸外观而得名)。这有时也被称为 StudlyCaps。

    注意:在 CapWords 中使用缩略语时,要将缩略语的所有字母大写。因此,HTTPServerError 比 HttpServerError 好。

  • mixedCase (与 CapitalizedWords 不同的是首字母小写的字符!)
  • Capitalized_Words_With_Underscores (丑陋!)

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

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

此外,还承认以下使用前导或尾部下划线的特殊形式(这些通常可以与任何大小写惯例相结合):

  • _single_leading_underscore:weak “内部使用” 指标。例如,from M import * 不会导入名称以下划线开头的对象。
  • single_trailing_underscore_:按惯例使用,以避免与 Python 关键字冲突,例如
    tkinter.Toplevel(master, class_='ClassName')
    
  • __double_leading_underscore:当命名一个类的属性时,会调用名称混用(在类 FooBar 中,__boo 变成 _FooBar__boo;见下文)。
  • __double_leading_and_trailing_underscore__:实时在用户控制的命名空间中的 “magic” 对象或属性。例如:__init____import____file__。不要发明这样的名字;只使用文件规定的名字。

规范性的:命名规则

应避免的名称

永远不要使用字符 ‘l’(小写字母 el)、’O’(大写字母 oh)或 ‘I’(大写字母 eye)作为单字符变量名。

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

ASCII 兼容性

Identifiers used in the standard library must be ASCII compatible as described in the policy section of PEP 3131.

包和模块名称

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

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

类名称

类名通常应使用 CapWords 惯例。

在接口被记录下来并主要作为可调用的情况下,可以用函数的命名惯例来代替。

请注意,对于内建程序的名称有一个单独的约定:大多数内建程序的名称是单字(或两个字并列),CapWords 约定只用于异常名称和内建程序常量。

类型变量名称

Names of type variables introduced in PEP 484 should normally use CapWords preferring short names: T, AnyStr, Num. It is recommended to add suffixes _co or _contra to the variables used to declare covariant or contravariant behavior correspondingly:

from typing import TypeVar

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

异常名称

因为异常应该是类,所以类的命名惯例在此适用。然而,你应该在你的异常名称上使用后缀 “Error”(如果异常实际上是一个错误)。

全局变量名称

(让我们希望这些变量只在一个模块内使用。)惯例与函数的惯例大致相同。

为通过 from M import * 使用而设计的模块应该使用 __all__ 机制来防止导出 globals,或者使用较早的惯例,在这种 globals 前加一个下划线(你可能想这样做,以表明这些 globals 是 模块非公共)。

函数和变量名称

函数名称应采用小写,必要时用下划线隔开,以提高可读性。

变量名称遵循与函数名称相同的惯例。

只允许在已经是主流风格的情况下使用 mixedCase(例如 threading.py),以保持向后兼容。

函数和方法的参数

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

总是使用 cls 作为类方法的第一个参数。

如果一个函数参数的名字与一个保留的关键字冲突,一般来说,最好在后面附加一个下划线,而不是使用缩写或拼写错误。因此 class_clss 好。(也许更好的是通过使用同义词来避免这种冲突。)

方法名称和实例变量

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

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

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

Python 将这些名字与类的名字混为一谈:如果类 Foo 有一个名为 __a 的属性,它就不能被 Foo.__a 访问。(一个执着的用户仍然可以通过调用 Foo._Foo__a 获得访问权。)一般来说,双引号应该只用来避免与设计为子类的类中的属性发生名称冲突。

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

常量

常量通常定义在模块层面,用大写字母书写,用下划线分隔单词。例如 MAX_OVERFLOWTOTAL

继承设计

一定要决定一个类的方法和实例变量(统称:”属性”(attributes))应该是公共的还是非公共的。如果有疑问,请选择非公共的;以后把它变成公共的比把公共属性变成非公共的更容易。

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

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

另一类属性是那些属于 “子类 API” 的属性(在其他语言中通常称为 “protected”)。有些类被设计成可以被继承,以扩展或修改该类行为的各个方面。在设计这样的类时,要注意明确决定哪些属性是公共的,哪些是子类 API 的一部分,哪些是真正只能由基类使用的。

考虑到这一点,下面是 Pythonic 的准则:

  • 公共属性不应该有前导下划线。
  • 如果你的公共属性名称与一个保留的关键字相冲突,请在你的属性名称后面加上一个下划线。这比缩写或破坏性的拼写更可取。(然而,尽管有这个规则,’cls’ 是任何已知为类的变量或参数的首选拼写,特别是类方法的第一个参数。)

    注 1:见上面关于类方法的参数名称建议。

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

    注意1:尽量保持功能行为不受副作用影响,尽管缓存等副作用一般来说是可以的。

    注意2:避免使用属性来进行计算上昂贵的操作;属性符号使调用者相信访问是(相对)便宜的。

  • 如果你的类打算被子类化,并且你有不想让子类使用的属性,考虑用双前导下划线和无尾部下划线来命名它们。这将调用 Python 的名称混合算法,类的名称将被混合成属性名称。这有助于在子类无意中包含相同名称的属性时,避免属性名称的冲突”

    注意1:注意只有简单的类名被用于混杂的名称中,所以如果一个子类同时选择相同的类名和属性名,你仍然可以得到名称碰撞。

    注意2:名字的拼写会使某些使用,如调试和 __getattr__(),变得不那么方便。然而,名称混合算法有很好的文件记录,并且很容易手动执行。

    注3:不是每个人都喜欢名字的拼写。尽量在避免意外的姓名冲突与高级调用者的潜在使用之间取得平衡。

公共和内部接口

任何向后兼容的保证只适用于公共接口。因此,重要的是,用户要能够明确区分公共接口和内部接口。

文档化的接口被认为是公开的,除非文档明确声明它们是临时的或内部的接口,免于通常的向后兼容性保证。所有没有记录的接口应该被认为是内部的。

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

即使适当地设置了 __all__,内部接口(包、模块、类、函数、属性或其他名称)仍应在前缀中加入一个单引号。

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

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

编程推荐

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

    例如,不要依赖 CPython 对 a += ba = a + b 形式的语句进行原地字符串连接的有效实现。这种优化即使在 CPython 中也是脆弱的(它只对某些类型有效),而且在不使用 refcounting 的实现中根本不存在。在库的性能敏感部分,应该使用 ''.join() 形式来代替。这将确保连接在不同的实现中以线性时间发生。

  • 对 None 这样的单子(singletons)的比较应该总是用 isis not 来进行,而不是用等号运算符。

    另外,当你真正的意思是 “如果 x 不是 None” 时,要小心写 “if x” – 例如,当测试一个默认为 None 的变量或参数是否被设置为其他值时。其他的值可能有一个类型(如容器),在布尔环境中可能是 false!

  • 使用 is not 运算符,而不是 not ... is。虽然这两个表达式在功能上是相同的,但前者更具可读性,是首选
    # Correct:
    if foo is not None:
    
    # Wrong:
    if not foo is None:
    
  • 当用丰富的比较实现排序操作时,最好是实现所有的六种操作(__eq____ne____lt____le____gt____ge__),而不是依靠其他代码只行使一个特定的比较。

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

    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 表达式与标识符绑定的赋值语句
    # Correct:
    def f(x): return 2*x
    
    # Wrong:
    f = lambda x: 2*x
    

    第一种形式意味着产生的函数对象的名称是具体的 ‘f’,而不是通用的 ‘<lambda>’。这对回溯和一般的字符串表示法更有用。使用赋值语句消除了 lambda 表达式比显式 def 语句所能提供的唯一好处(即它可以被嵌入到一个更大的表达式中)

  • Exception 派生异常,而不是 BaseException。直接继承自 BaseException 是为那些捕获异常几乎都是错误的事情而保留的。

    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)

    类的命名惯例适用于此,尽管如果异常是一个错误,你应该给你的异常类添加后缀 “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’ 子句的使用限制在两种情况下:

    1. 如果异常处理程序将打印出来或记录回溯;至少用户会意识到发生了错误。
    2. 如果代码需要做一些清理工作,但又让异常通过 raise 向上传播。try...finally 可以是处理这种情况的更好方法。
  • 当捕捉操作系统错误时,更喜欢 Python 3.3 中引入的显式异常层次,而不是对 errno 值的自省。
  • 此外,对于所有的 try/except 子句,将 try 子句限制在必要的绝对最小的代码量。同样,这也避免了掩盖错误 ::”
    # 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 语句也是可以接受的。
  • 上下文管理程序除了获取和释放资源外,还应该通过单独的函数或方法来调用
    # Correct:
    with conn.begin_transaction():
        do_stuff_in_transaction(conn)
    
    # Wrong:
    with conn:
        do_stuff_in_transaction(conn)
    

    后一个例子没有提供任何信息来表明 __enter____exit__ 方法除了在连接后关闭连接外,还在做其他事情。在这种情况下,明确说明是很重要的。

  • 在返回语句中要保持一致。在一个函数中,要么所有的返回语句都应该返回一个表达式,要么都不应该。如果任何返回语句都返回一个表达式,任何没有返回值的返回语句都应该明确说明为 return None,并且在函数的末尾应该有一个明确的返回语句(如果可以达到)
    # 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)
    
    # Wrong:
    
    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() 更干净,更不容易出错

    # Correct:
    if foo.startswith('bar'):
    
    # Wrong:
    if foo[:3] == 'bar':
    
  • 对象类型比较应该总是使用 isinstance(),而不是直接比较类型
    # Correct:
    if isinstance(obj, int):
    
    # Wrong:
    if type(obj) is type(1):
    
  • 对于序列,(字符串、列表、元组),使用空序列为 false 的事实
    # Correct:
    if not seq:
    if seq:
    
    # Wrong:
    if len(seq):
    if not len(seq):
    
  • 不要写依赖大量尾部空白的字符串字面。这样的尾部空白在视觉上是无法区分的,一些编辑器(或最近的 reindent.py)会修剪它们。
  • 不要用 == 来比较布尔值是真还是假
    # Correct:
    if greeting:
    
    # Wrong:
    if greeting == True:
    

    更糟糕的是

    # Wrong:
    if greeting is True:
    
  • 不鼓励在 try...finally 的 finally 套件中使用流程控制语句 return/break/continue,因为流程控制语句会跳出 finally 套件。这是因为这样的语句将隐含地取消任何正在通过 finally 套件传播的活动异常
    # Wrong:
    def foo():
        try:
            1 / 0
        finally:
            return 42
    

函数注解

With the acceptance of PEP 484, the style rules for function annotations have changed.

  • Function annotations should use PEP 484 syntax (there are some formatting recommendations for annotations in the previous section).
  • 不再鼓励以前在本 PEP 中推荐的注解样式的实验。
  • However, outside the stdlib, experiments within the rules of PEP 484 are now encouraged. For example, marking up a large third party library or application with PEP 484 style type annotations, reviewing how easy it was to add those annotations, and observing whether their presence increases code understandability.
  • Python 标准库在采用这种注解时应该是保守的,但对于新的代码和大的重构来说,允许使用它们。
  • 对于想要对函数注解进行不同使用的代码,建议放一个形式为
    # type: ignore
    

    near the top of the file; this tells type checkers to ignore all annotations. (More fine-grained ways of disabling complaints from type checkers can be found in PEP 484.)

  • 就像 linter 一样,类型检查器是可选的、独立的工具。默认情况下,Python 解释器不应该因为类型检查而发出任何消息,也不应该根据注解来改变它们的行为。
  • 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 [5].

变量注解

PEP 526 introduced variable annotations. The style recommendations for them are similar to those on function annotations described above:

  • 模块级变量、类和实例变量以及局部变量的注释应该在冒号后面有一个空格。
  • 冒号前不应该有空格。
  • 如果一个赋值有一个右侧,那么等号符号两边应该正好有一个空格
    # Correct:
    
    code: int
    
    class Point:
        coords: Tuple[int, int]
        label: str = '<unknown>'
    
    # 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).

脚注

参考


Source: https://github.com/python/peps/blob/main/pep-0008.txt

Last modified: 2022-05-11 17:45:05 GMT