简明 Python 代码格式指南(PEP8)

发布于:2022-01-09

一致的代码格式可以改善代码可读性。

代码布局 Layout

缩进 Indentation

Python 的缩进首选用空格,不可以混用 Tab 和空格缩进。

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

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个空格
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)

if (this_is_one_thing and
    that_is_another_thing):
    # 此处可以添加注释用于分隔
    do_something()

# 添加额外的缩进
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

多行结构下,用于结束的 方括号、括号、花括号 可以与上一行对齐,也可以与整个结构的开头对齐。

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

单行长度限制

每行代码最多 79 个字符,对于 docstring 或者注释,限制为 72 个字符。

括号内的内容通常可以通过换行来限制单行长度,其他的可以使用 \ 来进行分行。

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

二元操作符语句的断行

对于二元操作符,换行符应该位于操作符之前。

income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction)

空白行

  • 在顶级函数和类定义前后保留两个空行。
  • 类中方法定义前后保留一个空行。
  • 额外的空白行可用于对相关函数进行分组。
  • 函数内的空白行用于分隔不同的逻辑部分。

源文件编码

UTF-8!UTF-8!还是TM的 UTF-8!

imports

# 导入多个模块,应该使用多条 import 语句
import os
import sys

# 以下是错误的
import sys, os

# 使用 from-import 时无需分行
from subprocess import Popen, PIPE

import 语句处于文件顶端,紧靠在在模块注释之后,模块全局变量和常量之前。

import 按照以下顺序分组排放,分组之间空一行。

  1. import 标准库
  2. import 相关的第三方库
  3. import 本地模块
# 推荐使用 Absolute import
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

# Relative import 也可以使用
from . import sibling
from .sibling import example

# 尽量避免 import 通配符
from foo import *

__all__ __author__ __version__ 此类模块级的双下划线变量应该位于模块注释之后,所有 import 语句之前(除了 from 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

字符串引号

对于字符串使用单引号还是双引号,PEP 不做规定。

表达式和语句中的空格

# 左括号后,右括号前不加空格
spam(ham[1], {eggs: 2})         # 正确做法
spam( ham[ 1 ], { eggs: 2 } )   # ❌ 错误做法

# 括号内最后一个逗号后不加空格
foo = (0,)      # 正确做法
foo = (0, )     # ❌ 错误做法

# 逗号、分号、冒号前不加空格
if x == 4: print x, y; x, y = y, x          # 正确做法
if x == 4 : print x , y ; x , y = y , x     # ❌ 错误做法

# 对于切片操作的冒号,其左右的空格应该平衡(要么左右都有,要么都没有)
# 有两个冒号时,两个冒号采用相同的空格策略
# 当切片参数省略时,该参数左右的空格也要省略
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[1: 9], ham[1 :9], ham[1:9 :3]       # ❌ 错误做法
ham[lower : : upper]                    # ❌ 错误做法
ham[ : upper]                           # ❌ 错误做法

# 函数调用时的括号前不加空格
spam(1)     # 正确做法
spam (1)    # ❌ 错误做法

# 索引和切片操作的方括号前不加空格
dct['key'] = lst[index]         # 正确做法
dct ['key'] = lst [index]       # ❌ 错误做法

# 操作符左右最多保留一个空格
x = 1                   # 正确做法
long_variable = 3       # 正确做法
x             = 1       # ❌ 错误做法
long_variable = 3

避免行尾空格,否则可能会导致一些意想不到的问题。比如当 \ 后跟一个空格时,反斜线便会失去连接两行的作用。

始终在以下操作符左右保留一个空格:

  • 赋值操作符:= += -= *= /=
  • 比较操作符:== < > != <> <= >= in not in is is not
  • 布尔操作符:and or not

语句中存在不同优先级的操作符时,可以在低优先级的操作符左右添加空格,省略高优先级操作符左右的空格。 但是绝对不要使用一个以上的连续空格。

# 以下为正确做法
i = i + 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)

# 以下为 ❌ 错误做法
i=i+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): ...        # 正确做法
def complex(real, imag = 0.0): ...      # ❌ 错误做法

def munge(input: AnyStr = None): ...    # 正确做法
def munge(input: AnyStr=None): ...      # ❌ 错误做法

尽量不使用复合语句(一行写多个语句)。
一个语句如果拥有多个子语句,则绝对不能将其写在一行上。

# ❌ 错误做法
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()

# 以下为绝对的 ❌ 错误做法
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()

结尾逗号的使用

结尾逗号一般是可省略的,除非用在仅有一个元素的 Tuple 表达式中。

结尾逗号可以用在多行结构中,以便未来添加新元素。而在单行结构中,结尾逗号没有什么存在的意义。

# 正确做法
FILES = [
    'setup.cfg',
    'tox.ini',      # 未来添加新元素只需另起一行
]
initialize(FILES,
           error=True,
           )

# ❌ 错误做法
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

注释

保证良好的注释(清晰且易于理解),并在代码改动时及时更新对应注释。

注释应该为完整的句子。多行注释包含多个由完整句子组成的段落,其中每条句子都以句号结尾。

注释中的句号之后应该保留两个空格(最后一个句号除外)。

对于非英语国家程序员,同样建议使用英语书写注释,除非代码绝对不会被其他语言的人阅读。

块注释 (Block Comments)

块注释位于代码之上,两者同级,每一行代码由 # 外加一个空格开始(除非内部存在缩进)。

块注释中的段落之间由一个空注释分隔(仅包含一个 # 的行)。

# This is paragraph one, line one,
# paragraph one, line two.
#
# Paragraph two.

行内注释 (Inline Comments)

行内注释应当被谨慎使用。

行内注释与代码同行,注释与代码之间至少保留两个空格。

# ❌ 对于意义明显的代码无需再注释说明
x = x + 1                 # Increment x

# 有实际意义的注释可以保留
x = x + 1                 # Compensate for border

文档字符串 (Documentation Strings)

应当为所有公开的模块、函数、类、类方法书写 docstring。对于非公开方法,也应当在 def 之后对其注释(描述方法作用)。

PEP 257 指明了书写 docstring 的正确规范。

多行 docstring 结尾的 """ 应当独立成行,而单行 docstring 结尾的 """ 应当位于同一行。

"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""

"""Return an ex-parrot."""

命名约定

用于公共接口的名称应当尽可能的遵守命名约定,其比内部实现中的名称优先级要高。

命名风格

常见的命名风格有以下几种:

  • b 单个小写字母
  • B 单个大写字母
  • lowercase
  • lower_case_with_underscores
  • UPPERCASE
  • UPPER_CASE_WITH_UNDERSCORES
  • CapitalizedWords 又名 CapWords 或 CamelCase,驼峰命名法
    Note: 名称里的缩写最好使用全大写,比如 HTTPServer 要好于 HttpServer
  • mixedCase
  • Capitalized_Words_With_Underscores 很丑陋!\
  • _single_leading_underscore 一般表示内部使用,from X import * 不会导入开头为下划线的名称。
  • __double_leading_undersocre 当最为类属性时,实际名称会发生改变(比如类 FooBar 的 __boo 属性的实际名称会变为 _FooBar__boo)。
  • __double_leading_and_trailing_underscore__ 用作魔术对象/属性/方法,不要私自定义此类名称。

约定

单字符名称永远不要使用 小写L l、大写O O、大写i I 这类容易被误认的字符。

标准库中的标识符必须时 ASCII 兼容的(PEP 3131)。

包名和模块名应该使用较短的全小写名称(lowercase)。
对于模块名,在保证提升阅读性的前提下可以适当使用下划线。而对于包名则尽量不使用下划线。

Class 类名应该采用驼峰命名法(CapWords),而当一个类主要被用于调用(callable)时,可改用函数的命名约定。
对于 Python 内置名称,只有常量和异常名称采用驼峰命名法。

TypeVar 类型变量名一般使用较短的驼峰命名法(比如 T AnyStr Num), 对于协变量(covariant)或逆变量(contravariant)推荐使用 _co_contra 后缀。

from typing import TypeVar

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

Exception 异常通常是一个类,所以适用类名称约定,使用驼峰命名法,但要添加后缀 Error 明确该异常是一个 Error。

Global Variable 全局变量名与函数使用相同的命名约定。

Function & Variable 函数和变量名称使用全小写名称,可用下划线分隔单词。 mixedCase 这种命名风格只允许在已经存在这种风格的代码中使用(老代码兼容)。

Arguments 参数名称使用全小写。
对于实例方法,首个参数名必须为 self;对于类方法,首个参数名必须为 cls
如果参数名与保留关键字冲突,最好在参数名后添加一个下划线,比如 class_

Method & Instance Variable 方法名和实例变量名使用全小写名称,可用下划线分隔单词。
下划线开头表示不公开,双下划线开头表示启用 python 的名称修改规则(name mangling)。

Constants 常量名使用全大写名称,可用下划线分隔单词。比如:MAX_OVERFLOW TOTAL

继承设计

  • 公开属性不以下划线作为前缀。
  • 如果公开属性与保留关键字冲突,可在结尾添加下划线(最好还是改个名字)。
  • 对于公开的简单数据属性,最好直接暴露属性名,而非访问/修改方法。
    可以通过 property 方法将访问/修改方法隐藏在属性名之后,但要注意方法的副作用,以及不要在 property 方法中包含复杂耗时操作。
  • 如果属性名不想被子类使用,可添加两个下划线作为前缀,这会引发名称修改。

编程建议

代码不应依赖于某个具体实现(比如 PyPy、Jython、Cython 等)。

None 比较应该使用 is Noneis not None

使用 is not 而非 not ... is

if foo is not None: ...     # 正确做法
if not foo is None: ...     # ❌ 错误做法

对于复杂类型进行排序时,最好实现 __eq__() __ne__() __lt__() __le__() __gt__() __ge__() 这六个方法。

永远不要使用 lambda 表达式去替代一个函数声明。

异常应该从 Exception 而非 BaseException 继承。

适当的使用异常链(exception chaining)。
如果在抛出一个新异常时仍要保留之前的 traceback,应当使用 raise X from Y
如果需要新异常完全替代一个内部异常,则可使用 raise X from None

捕获异常时要指定异常名称,不要使用 except:
因为 except: 会捕获所有异常,包括 SystemExitKeyboardInterrupt,这可能会导致无法通过 Control-C 中断程序等问题。
如果想要捕获所有异常,请使用 except Exception:

当捕获系统异常时,应当使用明确的异常类型,而非异常的 errno 属性。

try 语句仅包含必要操作。

# 正确做法
try:
    value = collection[key]
except KeyError:
    return key_not_found(key)
else:
    return handle_value(value)

# ❌ 错误做法
try:
    # Too broad!
    return handle_value(collection[key])
except KeyError:
    # Will also catch KeyError raised by handle_value()
    return key_not_found(key)

使用 withtry/finally 语句确保一个资源使用后的清理。

保证 return 语句的一致性,即使什么也不返回,也要指定 return None

使用 ''.startswith()''.endswith() 检查字符串前缀后缀。

使用 isinstance 比较对象类型。

if isinstance(obj, int):        # 正确做法
if type(obj) is type(1):        # ❌ 错误做法

空序列本身即为 False,无需再通过 len() 判断序列是否为空。

# 正确做法
if not seq: ...
if seq: ...

# ❌ 错误做法
if len(seq):
if not len(seq):

不要将一个布尔值与 TrueFalse 比较。

if greeting: ...                # 正确做法
if greeting == True: ...        # ❌ 错误做法
if greeting is True: ...        # 绝对 ❌ 错误做法

不要在 finally 语句中使用 return/break/continue 这类控制流语句。

# ❌ 错误做法
def foo():
    try:
        1 / 0
    finally:
        # 此处的 return 会抑制异常向外传播
        return 42

函数和变量注解

最后这点参考 PEP 8 原文吧,不想写了。