go to index

PEP 8 – Style Guide for Python Code

read time 56 min read
Python

PEP 8 – Style Guide for Python Code

What’s PEP 8?

PEP 8, sometimes spelled PEP8 or PEP-8, is a document that provides guidelines and best practices on how to write Python code. It was written in 2001 by Guido van Rossum, Barry Warsaw, and Alyssa Coghlan. The primary focus of PEP 8 is to improve the readability and consistency of Python code.

PEP 8(有时写作 PEP8 或 PEP-8)是一个提供如何编写 Python 代码的指导和最佳实践的文档。它最初由 Guido van Rossum(Python 之父)、Barry Warsaw 和 Alyssa Coghlan 在 2001 年编写。PEP 8 的主要焦点是提高 Python 代码的可读性和一致性。

A Foolish Consistency is the Hobgoblin of Little Minds !

愚蠢的一致性是小心眼的恶魔!

Python之父Guido在Python代码风格规范的一个重要见解是->代码被阅读的次数会远高于被编写的次数,因此本指南(PEP8)旨在提高代码的可读性,并使其在各种Python代码中保持一致。

然而,PEP8是一份代码风格指南,与指南保持一致的风格很重要,但是项目内的代码一致性,则要远高于前者。一个模块内的代码风格保持一致是至关重要的。

什么时候应用PEP8,什么时候与项目其他代码保持一致的风格,需要你自己去思考

Code Lay-out - 代码布局

Indentation - 缩进

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

续行应该使用 Python 在圆括号、方括号和花括号内的隐式行连接来垂直对齐被包裹的元素,或者使用悬挂缩进。当使用悬挂缩进时,应考虑以下几点:第一行不应该有参数,并且应使用额外的缩进来清楚地标明它是一个续行:

悬挂缩进是一种排版风格,其中除了第一行之外,段落中的每一行都进行了缩进。在 Python 编程语言的语境中,这个术语用来描述一种样式,其中带括号语句的开括号是该行的最后一个非空白字符,随后的行缩进,直到闭括号。

代码示例

python
# 正确的代码示例
# 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.
# 添加4个空格(额外的缩进级别),以区分参数与其余部分。(var_one 到 def 一共是8个空格,两级缩进)
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)
python
# 错误的代码示例

# 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空格规则是可选的

python
# Hanging indents *may* be indented to other than 4 spaces.
# 悬挂缩进可以缩进为4个空格以外的其他长度
foo = long_function_name(
  var_one, var_two,
  var_three, var_four)

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

python
# 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.
    # 由于两个条件都为真,我们可以进行 frobnicate 操作。
    do_something()

# Add some extra indentation on the conditional continuation line.
# 在条件语句的续行上添加一些额外的缩进。
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

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

多行结构中的结束括号/方括号/圆括号可以排列在列表最后一行的第一个非空白字符下,如下所示:

python
my_list = [
    1, 2, 3,
    4, 5, 6,
    ]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
    )

或者它可能排列在启动多行构造的行的第一个字符下方,如下所示:

python
my_list = [
    1, 2, 3,
    4, 5, 6,
]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
)

Tabs or Spaces? - 制表符(tab)还是空格(space)?

空格是首选的缩进方法。

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

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

Maximum Line Length - 最大行长

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

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

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

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

一些团队强烈倾向于更长的行长度。对于主要由一个团队维护,并且该团队能在这个问题上达成一致意见的代码,将行长度限制提高到99个字符是可以的,前提是注释和文档字符串最大行长仍然在72个字符。

Python 标准库比较保守,要求限制最大行长为 79 个字符(文档字符串/注释为 72 个)。

换行较长的行的首选方法是使用 Python 的隐式行续行符(在括号、中括号和大括号内)。可以将表达式放在括号内,从而将较长的行拆分为多行。应优先使用这些符号,而不是使用反斜杠进行行续行。

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

python
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声明。

确保适当缩进续行。

Should a Line Break Before or After a Binary Operator? - 应当在二元运算符之前还是之后换行?

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

python
# 示例代码:错误的换行,运算符与其操作数相隔很远
# Wrong Code Style: operators sit far away from their operands 
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

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

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

python
# 正确的代码风格,易于将运算符与操作数匹配
# Correct Code Style: easy to match operators with operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

在 Python 代码中,允许在二元运算符之前或之后中断,只要约定在本地保持一致即可。对于新代码,建议采用 Knuth 的风格。

Blank Lines - 空行

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

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

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

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

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

Source File Encoding - 源文件编码格式

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

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

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

鼓励全球范围内的开源项目采用类似的政策。

Imports - 导入

  • imports 应当被放在单独的行上:

    python
    # Correct Code Style:
    import os
    import sys
    
    python
    # Wrong Code Style:
    import sys, os
    

    但是也有例外

    python
    # Correct Code Style:
    # 从 subprocess 模块中导入 Popen 类和 PIPE 常量。
    from subprocess import Popen, PIPE
    
  • imports总是放在文件的顶部,紧接着任何模块注释和文档字符串,以及模块全局变量和常量之前。

    imports应按以下顺序分组:

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

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

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

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

    但是,显式相对导入是绝对导入的一种可接受的替代方法,尤其是在处理复杂的包布局时,使用绝对导入会导致代码看起来有一种不必要地冗长:

    python
    from . import sibling
    from .sibling import example
    

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

  • 从包含类的模块导入类时,通常可以这样拼写:

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

    如果此拼写导致本地名称冲突,则请明确拼写:

    python
    import myclass
    import foo.bar.yourclass
    

    并使用myclass.MyClassfoo.bar.yourclass.YourClass

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

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

Module Level Dunder Names - 模块级别双下方法名

模块级别的“双下方法名”(即带有两前两后下划线的名称),如__all____author____version__等,应该放置在模块文档字符串之后,但在任何导入语句之前,__future__导入除外。Python规定,__future__导入必须出现在模块中的任何其他代码之前,除了文档字符串:

__future__ 导入是Python语言的一个特性,它允许你在当前版本的Python解释器中使用来自未来版本的Python(通常是下一个主要版本)的一些特性。这主要是为了提供向后兼容性,让开发者能够在当前版本的Python中平滑过渡到新的语法或行为。

python
"""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中,单引号字符串和双引号字符串是相同的。对于这一点,PEP8并没有对此做出推荐。选择一个规则并坚持下去,保持代码风格的一致性。然而,当一个字符串包含单引号或双引号字符时,使用另一种引号以避免在字符串中使用反斜杠。这将会提高代码的可读性。

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

Whitespace in Expressions and Statements - 表达式和语句中的空格

Pet Peeves - 讨厌的事

在下列情况下,请避免使用多余的空格:

  • 紧接着在圆括号、方括号或大括号内:

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

    python
    # Correct Code Style:
    foo = (0,)
    
    python
    # Wrong Code Style: 
    bar = (0, )
    
  • 紧接着逗号、分号或冒号之前:

    python
    # Correct Code Style:
    if x == 4: print(x, y); x, y = y, x
    
    python
    # Wrong Code Style:
    if x == 4 : print(x , y) ; x , y = y , x
    
  • 但是,在切片中,冒号的作用类似于二元运算符,并且两边的空格数应相等(将其视为优先级最低的运算符)。在扩展切片中,两个冒号必须应用相同数量的空格。例外:当省略切片参数时,空格也会被省略:

    python
    # Correct Code Style:
    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]
    
    python
    # Wrong Code Style:
    ham[lower + offset:upper + offset]
    ham[1: 9], ham[1 :9], ham[1:9 :3]
    ham[lower : : step]
    ham[ : upper]
    
  • 紧接着在函数调用的参数列表的开始括号之前:

    python
    # Correct Code Style:
    spam(1)
    
    python
    # Wrong Code Style:
    spam (1)
    
  • 紧接着开始索引或切片的开括号之前:

    python
    # Correct Code Style:
    dct['key'] = lst[index]
    
    python
    # Wrong Code Style:
    dct ['key'] = lst [index]
    
  • 赋值(或其他)运算符周围应有多个空格,以使其与另一个运算符对齐:

    python
    # Correct Code Style:
    x = 1
    y = 2
    long_variable = 3
    
    python
    # Wrong Code Style:
    x             = 1
    y             = 2
    long_variable = 3
    # or 
    x=1
    y  = 2
    long_variable = 3
    

其他建议

  • 避免在任何地方使用尾随空格。由于它通常是不可见的,因此可能会造成混淆:例如,反斜杠后跟空格和换行符不算作行延续标记。有些编辑器不保留它,许多项目(如 CPython 本身)都有拒绝它的预提交钩子。

  • 始终要在这些二元运算符的两侧各保留一个空格:赋值(=)、增量赋值(+=, -= 等)、比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not)、布尔逻辑(and, or, not)。

  • 如果使用具有不同优先级的运算符,请考虑在优先级最低的运算符周围添加空格。请自行判断;但是,切勿使用超过一个空格,并且二元运算符两侧的空格量应始终相同:

    python
    # Correct Code Style:
    i = i + 1
    submitted += 1
    x = x*2 - 1
    hypot2 = x*x + y*y
    c = (a+b) * (a-b)
    
    python
    # Wrong Code Style:
    i=i+1
    submitted +=1
    x = x * 2 - 1
    hypot2 = x * x + y * y
    c = (a + b) * (a - b)
    
  • 函数注释应使用冒号的正常规则,并且->如果存在箭头,则箭头周围始终留有空格。(有关函数注释的更多信息,请参阅 下面的函数注释。):

    python
    # Correct Code Style:
    def munge(input: AnyStr): ...
    def munge() -> PosInt: ...
    
    python
    # Wrong Code Style:
    def munge(input:AnyStr): ...
    def munge()->PosInt: ...
    
  • =当用于指示关键字参数或用于指示 未注释的函数参数的默认值时,请勿在符号周围使用空格:

    python
    # Correct Code Style:
    def complex(real, imag=0.0):
        return magic(r=real, i=imag)
    
    python
    # Wrong Code Style:
    def complex(real, imag = 0.0):
        return magic(r = real, i = imag)
    

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

    python
    # Correct Code Style:
    def munge(sep: AnyStr = None): ...
    def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
    
    python
    # Wrong Code Style:
    def munge(input: AnyStr=None): ...
    def munge(input: AnyStr, limit = 1000): ...
    
  • 通常不鼓励使用复合语句(同一行上的多个语句):

    python
    # Correct Code Style:
    if foo == 'blah':
        do_blah_thing()
    do_one()
    do_two()
    do_three()
    

    强烈不建议

    python
    # Wrong Code Style:
    if foo == 'blah': do_blah_thing()
    do_one(); do_two(); do_three()
    
  • 虽然有时可以将 if/for/while 和小主体放在同一行中,但对于多子句语句,切勿这样做。还要避免折叠这么长的行!

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

    强烈不建议

    python
    # Wrong Code Style:
    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()
    

When to Use Trailing Commas - 何时使用尾随逗号

尾随逗号通常是可选的,但在创建一个元素的元组时它们是必需的。为了清楚起见,建议将后者括在(技术上多余的)括号中:

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

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

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

Comments - 注释

与代码相矛盾的注释比没有注释更糟糕。当代码变更时,始终要优先考虑更新注释!

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

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

在多句注释中,除了最后一句外,你应该在句号后使用一个或两个空格。

确保你的注释清晰,易于其他使用你编写的语言的人理解。

来自非英语国家的Python程序员:请用英语写注释,除非你百分之百确定代码永远不会被不说你语言的人阅读。

Block Comments - 块注释

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

块注释内的段落通过包含单个井号(#)的行来分隔。

Inline Comments - 行内注释

适量使用行内注释。

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

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

python
x = x + 1                 # x自增

但有时,这样做是非常有用的

python
x = x + 1                 # 边界补偿

Documentation Strings - 文档字符串

编写优秀文档字符串(即“docstrings”)的约定在 PEP 257 中得到了明确。

  • 为所有公共模块、函数、类和方法编写文档字符串(docstrings)。对于非公共方法,不需要编写文档字符串,但应该有一段注释来描述该方法的功能。这段注释应该出现在 def 行之后。

  • PEP 257 描述了良好的文档字符串(docstring)编写规范。请注意,最重要的是,结束多行文档字符串的三个双引号 """ 应该独占一行:

    python
    def my_function():
        """
        This is a docstring that explains what the function does.
        It can span multiple lines, and should provide information about
        the function's arguments, return values, and any exceptions that
        it might raise.
        """
        pass
    
  • 对于单行文档字符串,请将结束的 """ 保留在同一行:

    python
    def my_function():
        """这是一个简短的文档字符串,描述函数的功能。"""
        pass
    

Naming Conventions - 命名约定

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

Overriding Principle - 覆盖原则

作为 API 公开部分对用户可见的名称应该遵循反映使用方式而非实现方式的约定。

Descriptive: Naming Styles - 描述性:命名风格

存在许多不同的命名风格。能够识别使用的是哪种命名风格,而不管它们用于什么,这是有帮助的。

常见的命名风格通常可以区分为以下几种:

以下是一些常见的命名风格,通常用于编程中以提高代码的可读性和一致性:

  1. b (单小写字母):使用单个小写字母作为变量名,如 a, b, i

  2. B (单大写字母):使用单个大写字母作为变量名,如 A, B, I

  3. lowercase:全部小写字母,如 lowercase

  4. lower_case_with_underscores:小写字母与下划线,如 lower_case_with_underscores

  5. UPPERCASE:全部大写字母,如 UPPERCASE

  6. UPPER_CASE_WITH_UNDERSCORES:大写字母与下划线,如 UPPER_CASE_WITH_UNDERSCORES

  7. CapitalizedWords (或 CapWords, 或 CamelCase):首字母大写的驼峰式命名,因字母看起来像驼峰而得名。如 CapitalizedWords。有时也称为 StudlyCaps。

    注意:当在 CapWords 中使用缩写时,应将缩写的所有字母都大写。因此,HTTPServerErrorHttpServerError 更好。

  8. mixedCase (与 CapitalizedWords 的区别在于首字母小写!):混合大小写,首字母小写,如 mixedCase

  9. Capitalized_Words_With_Underscores:大写字母与下划线,看起来不太美观,如 Capitalized_Words_With_Underscores

还存在一种风格,即使用一个短的独特前缀来将相关的名称组合在一起。这在Python中不太常用,但为了完整性而提及。例如,os.stat() 函数返回一个元组,其元素传统上具有像 st_modest_sizest_mtime 等名称。(这样做是为了强调与POSIX系统调用结构字段的对应关系,这有助于熟悉该结构的程序员。)

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

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

  • _single_leading_underscore(单个前导下划线):表示弱“内部使用”的指示器。例如,使用 from M import * 时不会导入以单个下划线开头的对象名称。

  • single_trailing_underscore_(单个尾随下划线):按照惯例使用,以避免与Python关键字发生冲突,例如:

    python
    tkinter.Toplevel(master, class_='ClassName')
    
  • __double_leading_underscore(双前导下划线):在命名类属性时,会触发名称改写(name mangling),在类 FooBar 内部,__boo 变为 _FooBar__boo;详见下文。

  • double_leading_and_trailing_underscore(双前导和尾随下划线):存在于用户控制的命名空间中的“魔法”对象或属性。例如,__init____import____file__。永远不要发明这样的名称;只在文档中有所记载时使用它们。

Prescriptive: Naming Conventions - 指令性:命名约定

Names to Avoid - 避免使用的命名

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

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

ASCII Compatibility - 兼容ASCII字符集

标准库中使用的标识符必须符合 PEP 3131 中策略部分所描述的 ASCII 兼容性要求

Package and Module Names - 包和模块命名

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

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

Class Name - 类的命名

类名通常应该使用 CapWords(大驼峰式)命名约定。

在接口被文档记录,并且主要作为一个可调用的函数使用的情况下,可以使用函数的命名约定来命名。

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

Type Variable Names - 类型变量命名

PEP 484 引入的类型变量名称通常应该使用 CapWords(大驼峰式)命名约定,并倾向于使用简短的名称:例如 T、AnyStr、Num。建议向用于声明协变或逆变的变量添加后缀 _co 或 _contra,以相应地表示它们的变异行为:

python
from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True)  # 协变类型变量
KT_contra = TypeVar('KT_contra', contravariant=True)  # 逆变类型变量
Exception Names - 异常名称

由于异常应该是类,因此这里适用类命名约定。然而,如果你的异常确实是一种错误,那么你应该在异常名称后面加上后缀“Error”。

Global Variable Names - 全局变量命名

(希望这些变量仅用于在一个模块内部使用。)这些约定与函数的命名约定大致相同。

设计用于通过 from M import * 进行导入的模块应该使用 __all__ 机制来防止全局变量被导出,或者使用较旧的约定,即在这些全局变量名前加上下划线(你可能想要这样做来表示这些全局变量是“模块非公开的”)。

Function and Variable Names - 函数和变量命名

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

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

只有在已经采用该风格的上下文中才允许使用 mixedCase(混合大小写),例如为了保持向后兼容性,在 threading.py 中使用。

Function and Method Arguments - 函数和方法参数的命名

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

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

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

Method Names and Instance Variables - 方法名称和实例变量的命名

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

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

为了避免与子类中的名称冲突,使用两个前导下划线来触发 Python 的名称改写(name mangling)规则。

Python 会将这些名称与类名结合进行改写:如果类 Foo 有一个名为 __a 的属性,它不能通过 Foo.__a 来访问。(一个坚持的用户仍然可以通过调用 Foo._Foo__a 来访问。)通常,双前导下划线应用于避免在设计为可被子类化的类中与属性名称发生冲突。

注意:关于 __names(即以单个下划线开头的名称)的使用存在一些争议,如下所述。

Constants - 常量

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

Designing for Inheritance - 设计继承机制

设计继承时,请始终决定一个类的方法和实例变量(统称:“属性”)应该是公开的还是非公开的。如果犹豫不决,请选择非公开的;以后将其设为公开的比将公开属性设为非公开的要容易。

公开属性是你期望无关的客户端使用你的类时会用到的,你承诺避免进行不向后兼容的更改。非公开属性是无意让第三方使用的;你不保证非公开属性不会更改甚至被移除。

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

属性的另一个类别是那些属于“子类 API”的部分(在其他语言中通常称为“受保护的”)。有些类被设计为可以被继承,无论是为了扩展还是修改类的行为。在设计这样的类时,要明确决定哪些属性是公开的,哪些是子类 API 的一部分,哪些真正只应该由你的基类使用。

考虑到这些,以下是 Pythonic 的指导原则:

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

  • 如果你的公开属性名称与保留关键字冲突,在你的属性名称后附加一个单独的下划线。这比缩写或拼写错误更可取。(然而,尽管有这条规则,‘cls’ 仍然是任何已知为类的变量或参数的首选拼写,特别是类方法的第一个参数。)

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

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

    注意 1:尝试保持功能行为无副作用,尽管像缓存这样的副作用通常是可以的。

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

  • 如果你的类打算被继承,并且你有不希望子类使用的属性,请考虑用双前导下划线和无尾随下划线命名它们。这触发了 Python 的名称改写算法,将类的名称改写为属性名称。这有助于避免属性名称冲突,以防子类无意中包含同名的属性。

  • 注意 1:请注意,改写的名称中只使用了简单的类名,所以如果子类选择相同的类名和属性名,你仍然可能会遇到名称冲突。

  • 注意 2:名称改写可能会使某些使用(如调试和 __getattr__())变得不那么方便。然而,名称改写算法文档完备且易于手动执行。

  • 注意 3:并非每个人都喜欢名称改写。尝试在避免意外名称冲突与潜在的高级调用者使用之间取得平衡。

Public and Internal Interfaces - 公开和内部接口

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

有文档记录的接口被视为公开的,除非文档明确声明它们是临时的或内部接口,这些接口不受通常的向后兼容性保证。所有未记录的接口应被视为内部的。

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

即使 __all__ 被适当设置,内部接口(包、模块、类、函数、属性或其他名称)仍应在名称前加上单个前导下划线。

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

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

Programming Recommendations - 编程建议

  • 代码应该以一种不会对其他Python实现(PyPy、Jython、IronPython、Cython、Psyco等)造成不利的方式编写。

    例如,不要依赖于CPython对**in-place string concatenation **的高效实现,形式如 a += ba = a + b 的语句。即使在CPython中,这种优化也是脆弱的(它只对某些类型有效),并且在不使用引用计数的实现中根本不存在。在库的性能敏感部分,应该改用 ''.join() 形式。这将确保在不同实现中连接操作以线性时间进行。

  • 与像None这样的单例进行比较时,应始终使用 isis not,绝不要使用等式运算符。

    此外,当你实际想表达的的意思是 if x is not None时,注意不要写做 if x —— 例如,当测试一个默认为None的变量或参数是否被设置为其他值时。其他值可能有某种类型(比如容器),在布尔上下文中可能为False

  • 使用 is not 操作符,而不是 not ... is。虽然这两个表达式在功能上是相同的,但前者更易读且更受推荐:

    python
    # Correct Code Style:
    if foo is not None:
    
    python
    # Wrong Code Style:
    if not foo is None:
    
  • 在实现具有丰富比较的排序操作时,最好实现全部六个操作(__eq__, __ne__, __lt__, __le__, __gt__, __ge__)而不是依赖其他代码只执行特定的比较。

    为了最小化所涉及的工作量,functools.total_ordering() 装饰器提供了一个工具来生成缺失的比较方法。

    PEP 207 指出 Python 假设了自反性规则。因此,解释器可能会交换 y > xx < yy >= xx <= y,并且可能会交换 x == yx != y 的参数。sort() 操作保证使用 < 运算符,而 max() 函数使用 > 运算符。然而,最好实现全部六个操作,以避免在其他上下文中产生混淆。

  • 总是使用 def 语句,而不是使用将 lambda 表达式直接绑定到标识符的赋值语句:

    python
    # Correct Code Style:
    def f(x): return 2*x
    
    python
    # Wrong Code Style:
    f = lambda x: 2*x
    

    第一种形式意味着结果函数对象的名称明确为 f,而不是通用的 <lambda>。这对于追踪栈和一般的字符串表示更有用。使用赋值语句消除了 lambda 表达式相对于显式 def 语句可以提供的唯一好处(即它可以嵌入在更大的表达式中)。

  • 派生异常时应从 Exception 而不是 BaseException 继承。直接从 BaseException 继承是保留给那些捕获它们几乎总是错误的情况的异常。 设计异常层次结构应基于捕获异常的代码可能需要的区别,而不是异常被引发的位置。目标是程序性地回答“出了什么问题?”而不是仅仅声明“出现了问题”(参见 PEP 3151,了解内置异常层次结构中吸取这一教训的例子)。

    类命名约定在这里适用,但如果异常是错误,你应该在异常类中添加后缀“Error”。用于非本地流程控制或其他形式的信号传递的非错误异常不需要特殊后缀。

  • 适当地使用异常链。raise X from Y 应该用来表示明确的替换,同时不丢失原始的追踪栈。

    当故意替换内部异常(使用 raise X from None)时,确保将相关细节转移到新的异常中(例如,在将 KeyError 转换为 AttributeError 时保留属性名称,或者在新异常消息中嵌入原始异常的文本)。

  • 在捕获异常时,尽可能指定具体的异常,而不是使用裸的 except: 子句:

    python
    try:
        import platform_specific_module
    except ImportError:
        platform_specific_module = None
    

    一个裸的 except: 子句会捕获 SystemExitKeyboardInterrupt 异常,这会使得使用 Control-C 来中断程序变得更加困难,并且可能会掩盖其他问题。如果你想捕获所有表示程序错误的异常,请使用 except Exception:(裸的 except 等同于 except BaseException:)。

    一个经验法则是将裸的 except 子句的使用限制在两种情况:

    1. 如果异常处理器将打印或记录追踪栈;至少用户会意识到发生了错误。
    2. 如果代码需要进行一些清理工作,但随后让异常向上传播使用 raise。在这种情况下,try...finally 可能是一个更好的处理方式。
  • 当捕获操作系统错误时,更推荐使用 Python 3.3 中引入的显式异常层次结构,而不是对 errno 值进行内省。

  • 此外,对于所有的 try/except 子句,应将 try 子句限制在绝对必要的最小代码量。同样,这避免了掩盖错误:

    python
    # Correct Code Style:
    try:
        value = collection[key]
    except KeyError:
        return key_not_found(key)
    else:
        return handle_value(value)
    
    python
    # Wrong Code Style:
    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 语句也是可接受的。

  • 当上下文管理器执行的不仅仅是获取和释放资源的操作时,应该通过单独的函数或方法来调用它们

    python
    # Correct Code Style:
    with conn.begin_transaction():
        do_stuff_in_transaction(conn)
    
    python
    # Wrong Code Style:
    with conn:
        do_stuff_in_transaction(conn)
    

    后者示例没有提供任何信息来表明 __enter____exit__ 方法除了在事务后关闭连接之外还在执行其他操作。在这种情况下,明确性是重要的。

  • 在返回语句中保持一致性。要么函数中的所有返回语句都返回一个表达式,要么就都不返回。如果任何返回语句返回了一个表达式,那么在那些没有返回值的返回语句中应该显式地写为 return None,并且在函数的末尾(如果可达)应该有明确的返回语句:

    python
    # Correct Code Style:
    
    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)
    
    python
    # 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() 更为清晰,也更不容易出错:

    python
    # Correct Code Style:
    if foo.startswith('bar'):
    
    python
    # Wrong:
    if foo[:3] == 'bar':
    
  • 对象类型比较应该总是使用 isinstance() 而不是直接比较类型。

    python
    # Correct:
    if isinstance(obj, int):
    
    python
    # Wrong:
    if type(obj) is type(1):
    
  • 对于序列(字符串、列表、元组),使用空序列为假值的事实:

    python
    # Correct:
    if not seq:
    if seq:
    
    python
    # Wrong:
    if len(seq):
    if not len(seq):
    
  • 不要编写依赖于末尾空格的字符串字面量。这种末尾空格在视觉上无法区分,并且一些编辑器(或者最近,reindent.py 工具)会修剪它们。

  • 不要使用 == 将布尔值与 TrueFalse 进行比较

    python
    # Correct:
    if greeting:
    
    python
    # Wrong:
    if greeting == True:
    

    更糟糕的是

    python
    # Wrong:
    if greeting is True:
    
  • try...finally 结构的 finally 子句中使用流程控制语句 return/break/continue 是不推荐的,因为这些语句会跳出 finally 子句,从而隐式地取消在 finally 子句中传播的任何活动异常:

    python
    # Wrong:
    def foo():
        try:
            1 / 0
        finally:
            return 42
    

Function Annotations - 函数注解

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

  • 函数注解应该使用PEP 484语法(在前一节中有关于注解的一些格式化建议)。

  • 之前在本 PEP 中推荐的尝试不同注解风格的做法不再被鼓励。

  • 然而,在标准库之外,现在鼓励在PEP 484规则内进行实验。例如,对大型第三方库或应用程序使用PEP 484风格的类型注解进行标记,审查添加这些注解的难易程度,并观察它们的存在是否提高了代码的可理解性。

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

  • 对于想要对函数注解进行不同使用的代码,建议在文件顶部添加如下形式的注释:

    python
    # type: ignore
    

    这告诉类型检查器忽略所有注解。(PEP 484中可以找到更细粒度的禁用类型检查器抱怨的方法。)

  • 像 linter 一样,类型检查器是可选的、独立的工具。Python 解释器默认不应因类型检查而发出任何消息,也不应基于注解改变它们的行为。

  • 不想要使用类型检查器的用户可以忽略它们。然而,预计第三方库包的用户可能希望对这些包运行类型检查器。为此,PEP 484推荐使用 stub 文件:.pyi 文件,它们被类型检查器优先读取而不是相应的 .py 文件。Stub 文件可以与库一起分发,或者(在获得库作者许可的情况下)通过 typeshed 仓库分开分发。

Variable Annotations - 变量注解

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

  • 模块级变量、类变量、实例变量和局部变量的注解在冒号后应该有一个空格。

  • 冒号前不应该有空格。

  • 如果赋值语句有右侧部分,那么等号两侧应该恰好有一个空格:

    python
    # Correct:
    
    code: int
    
    class Point:
        coords: Tuple[int, int]
        label: str = '<unknown>'
    
    python
    # Wrong:
    
    code:int  # No space after colon
    code : int  # Space before colon
    
    class Test:
        result: int=0  # No spaces around equality sign
    
  • 尽管PEP 526已经被接受用于 Python 3.6,但变量注解语法是所有版本的 Python 中 stub 文件的首选语法(详见PEP 484)。

Conclusion - 结语

Python 编程语言已经成为许多人的首选编程语言之一。它是一种相对容易学习的语言,支持多种编程范式,拥有大量开源模块,增强了语言的实用性,并且在数据科学和网络开发社区中是首选工具。

然而,只有当你知道如何用代码更好地表达你的想法时,你才能充分利用 Python 的优势。

PEP 8 的目的是提高 Python 代码的一致性和可读性,使得不同开发者编写的代码风格保持统一,同时也使得新加入项目的开发者能够更容易地理解和维护现有代码。

遵循 PEP 8 编码规范是 Python 社区的普遍实践,尽管它不是强制性的,但大多数 Python 开发者和项目都会遵守这些指南。