简明 Python 代码格式指南(PEP8)
一致的代码格式可以改善代码可读性。
代码布局 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 按照以下顺序分组排放,分组之间空一行。
- import 标准库
- import 相关的第三方库
- 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 None
或 is 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:
会捕获所有异常,包括 SystemExit
和 KeyboardInterrupt
,这可能会导致无法通过 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)
使用 with
或 try/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):
不要将一个布尔值与 True
或 False
比较。
if greeting: ... # 正确做法
if greeting == True: ... # ❌ 错误做法
if greeting is True: ... # 绝对 ❌ 错误做法
不要在 finally
语句中使用 return/break/continue
这类控制流语句。
# ❌ 错误做法
def foo():
try:
1 / 0
finally:
# 此处的 return 会抑制异常向外传播
return 42
函数和变量注解
最后这点参考 PEP 8 原文吧,不想写了。