Introduction 介绍
官方原文: Python PEP8
本文提供的Python代码编码规范基于Python主要发行版本的标准库。
许多项目有自己的编码规范,在出现规范冲突时,项目自身的规范优先。
A Foolish Consistency is the Hobgoblin of Little Minds 呆板的坚持一致性是傻的没边了
“Readability counts” 可读性很重要
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is the most important.
风格指南是关于一致性的。这份风格指南的一致性是重要的。项目一致性更重要。模块或函数的一致性最重要。
需要知道什么时候应该不一致,有时候编码规范的建议并不适用。有疑问时,使用自己的最佳判断。看看其他的示例再决定。
不要为了遵守PEP约定而破坏兼容性!
Code lay-out 代码布局
Indentation 缩进
每一级缩进使用4个空格。
续行应该与其包裹元素对齐,要么使用圆括号、方括号和花括号内的隐式行连接来垂直对齐,要么使用挂行缩进对齐3。当使用挂行缩进时,应该考虑到第一行不应该有参数,以及使用缩进以区分自己是续行。
# 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)
四空格的规则对于续行是可选的。
# Hanging indents *may* be indented to other than 4 spaces. 悬挂缩进不一定要用4空格;这里两空格
foo = long_function_name(
var_one, var_two,
var_three, var_four)
当if语句的条件部分长到需要换行时,可以加括号:
# add extra indentation
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',
)
Tabs or Spaces? 制表符还是空格?
空格是首选
Maximum Line Length 行的最大长度
所有行限制的最大字符数为79。
没有结构化限制的大块文本(文档字符或者注释),每行的最大字符数限制在72。
一些团队更喜欢较长的行宽,可以把行长度从80增加到100个字符(更有效的做法是将行最大长度增加到99个字符),前提是注释和文档字符串依然已72字符折行。
通过小括号内表达式的换行方式将长串折成多行。这种方式应该优先使用,而不是使用反斜杠续行。
反斜杠有时依然很有用。比如,比较长的,多个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())
Should a line break before or after a binary operator? 在二元运算符之前应该换行吗?
# Wrong:
# operators sit far away from their operands
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
# Correct:
# easy to match operators with operands
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
Blank Lines 空行
顶层函数和类的定义,前后用两个空行隔开。
类里的方法定义用一个空行隔开。
相关的功能组可以用额外的空行(谨慎使用)隔开。一堆相关的单行代码之间的空白行可以省略(例如,一组虚拟实现 dummy implementations)。
在函数中使用空行来区分逻辑段(谨慎使用)。
Source File Encoding 源文件编码
Python核心发布版本中的代码总是以UTF-8格式编码(或者在Python2中用ASCII编码)。
使用ASCII(在Python2中)或UTF-8(在Python3中)编码的文件不应具有编码声明。
import 导入
# Correct:
import os
import sys
# Wrong:
import sys, os
# Correct:
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
标准库要避免使用复杂的包引入结构,而总是使用绝对路径。
不应该使用隐式相对路径导入,并且在Python 3中删除了它。
当从一个包含类的模块中导入类时,常常这么写:
from myclass import MyClass
from foo.bar.yourclass import YourClass
如果上述的写法导致名字的冲突,那么这么写:
import myclass
import foo.bar.yourclass
避免通配符的导入(from import *),因为这样做会不知道命名空间中存在哪些名字,会使得读取接口和许多自动化工具之间产生混淆。对于通配符的导入,有一个防御性的做法,即将内部接口重新发布为公共API的一部分(例如,用可选加速器模块的定义覆盖纯Python实现的接口,以及重写那些事先不知道的定义)。
当以这种方式重新发布名称时,以下关于公共和内部接口的准则仍然适用。
Module level dunder names 模块级的“呆”名
像__all__ , author , version 等这样的模块级“呆名“(也就是名字里有两个前缀下划线和两个后缀下划线),应该放在文档字符串的后面,以及除from future 之外的import表达式前面。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中,单引号和双引号字符串是相同的。
对于三引号字符串,总是使用双引号字符来与PEP 257中的文档字符串约定保持一致。
Whitespace in Expressions and Statements 表达式和语句中的空格
# 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]
Other Recommendations 其他建议
不要在尾部添加空格。因为尾部空格通常都=看不见,会产生混乱。
在二元运算符两边加一个空格:赋值(=),增量赋值(+=,-=),比较(==,<,>,!=,<>,<=,>=,in,not,in,is,is not),布尔(and, or, 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): ...
Comments 注释
Block Comments 块注释
块注释的每一行开头使用一个#和一个空格
Inline Comments 行内注释
行内注释和代码至少要有两个空格分隔。注释由#和一个空格开始。
x = x + 1 # Increment x
Documentation Strings 文档字符串
三个双引号;对于单行的文档说明,尾部的三引号应该和文档在同一行。
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
Naming Conventions 命名规范
Python库的命名规范很乱,从来没能做到完全一致。但是目前有一些推荐的命名标准。
Overriding Principle 最重要的原则
- need update below
那些暴露给用户的API接口的命名,应该遵循反映使用场景而不是实现的原则。
Descriptive: Naming Styles 描述:命名风格
常见的命名方式:
b(单个小写字母)
B(单个大写字母)
lowercase 小写字母
lower_case_with_underscores 使用下划线分隔的小写字母
UPPERCASE 大写字母
UPPER_CASE_WITH_UNDERSCORES 使用下划线分隔的大写字母
CapitalizedWords(或者叫 CapWords,或者叫CamelCase 驼峰命名法)。
mixedCase(不同于首字母大写,第一个单词的首字母小写)
Capitalized_Words_With_Underscores(巨丑无比!)
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:(双下划线开头,双下划线结尾)“magic”对象或者存在于用户控制的命名空间内的属性,例如:init,import__或者__file。除了作为文档之外,永远不要命这样的名。
Package and Module Names 包名和模块名
模块应该用简短全小写的名字,如果为了提升可读性,下划线也是可以用的。Python包名也应该使用简短全小写的名字,但不建议用下划线。
Class Names 类名
类名一般使用首字母大写的约定。
在接口被文档化并且主要被用于调用的情况下,可以使用函数的命名风格代替。
注意,对于内置的变量命名有一个单独的约定:大部分内置变量是单个单词(或者两个单词连接在一起),首字母大写的命名法只用于异常名或者内部的常量。
Exception Names 异常名
因为异常一般都是类,所有类的命名方法在这里也适用。然而,你需要在异常名后面加上“Error”后缀(如果异常确实是一个错误)。
Global Variable Names 全局变量名
(我们希望这一类变量只在模块内部使用。)约定和函数命名规则一样。
通过 from M import * 导入的模块应该使用all机制去防止内部的接口对外暴露,或者使用在全局变量前加下划线的方式(表明这些全局变量是模块内非公有)。
Function Names 函数名
函数名应该小写,如果想提高可读性可以用下划线分隔。
大小写混合仅在为了兼容原来主要以大小写混合风格的情况下使用(比如 threading.py),保持向后兼容性。
Function and method arguments 函数和方法参数
始终要将 self 作为实例方法的的第一个参数。
始终要将 cls 作为类静态方法的第一个参数。
如果函数的参数名和已有的关键词冲突,在最后加单一下划线比缩写或随意拼写更好。因此 class_ 比 clss 更好。(也许最好用同义词来避免这种冲突)
Method Names and Instance Variables 方法名和实例变量
遵循这样的函数命名规则:使用下划线分隔小写单词以提高可读性。
在非共有方法和实例变量前使用单下划线。
通过双下划线前缀触发Python的命名转换规则来避免和子类的命名冲突。
Python通过类名对这些命名进行转换:如果类 Foo 有一个叫 __a 的成员变量, 它无法通过 Foo.__a 访问。(执着的用户可以通过 Foo._Foo__a 访问。)一般来说,前缀双下划线用来避免类中的属性命名与子类冲突的情况。
注意:关于__names的用法存在争论(见下文)。
Constants 常量
常量通常定义在模块级,通过下划线分隔的全大写字母命名。例如: MAX_OVERFLOW 和 TOTAL。
Designing for inheritance 继承的设计
始终要考虑到一个类的方法和实例变量(统称:属性)应该是共有还是非共有。如果存在疑问,那就选非共有;因为将一个非共有变量转为共有比反过来更容易。
公共属性是那些与类无关的客户使用的属性,并承诺避免向后不兼容的更改。非共有属性是那些不打算让第三方使用的属性;你不需要承诺非共有属性不会被修改或被删除。
我们不使用“私有(private)”这个说法,是因为在Python中目前还没有真正的私有属性(为了避免大量不必要的常规工作)。
另一种属性作为子类API的一部分(在其他语言中通常被称为“protected”)。有些类是专为继承设计的,用来扩展或者修改类的一部分行为。当设计这样的类时,要谨慎决定哪些属性时公开的,哪些是作为子类的API,哪些只能在基类中使用。
贯彻这样的思想,一下是一些让代码Pythonic的准则:
公共属性不应该有前缀下划线。
如果公共属性名和关键字冲突,在属性名之后增加一个下划线。这比缩写和随意拼写好很多。(然而,尽管有这样的规则,在作为参数或者变量时,‘cls’是表示‘类’最好的选择,特别是作为类方法的第一个参数。)
注意1:参考之前的类方法参数命名建议
对于单一的共有属性数据,最好直接对外暴露它的变量名,而不是通过负责的 存取器(accessor)/突变(mutator) 方法。请记住,如果你发现一个简单的属性需要成长为一个功能行为,那么Python为这种将来会出现的扩展提供了一个简单的途径。在这种情况下,使用属性去隐藏属性数据访问背后的逻辑。
注意1:属性只在new-style类中起作用。
注意2:尽管功能方法对于类似缓存的负面影响比较小,但还是要尽量避免。
注意3:属性标记会让调用者认为开销(相当的)小,避免用属性做开销大的计算。
如果你的类打算用来继承的话,并且这个类里有不希望子类使用的属性,就要考虑使用双下划线前缀并且没有后缀下划线的命名方式。这会调用Python的命名转换算法,将类的名字加入到属性名里。这样做可以帮助避免在子类中不小心包含了相同的属性名而产生的冲突。
注意1:只有类名才会整合进属性名,如果子类的属性名和类名和父类都相同,那么你还是会有命名冲突的问题。
注意2:命名转换会在某些场景使用起来不太方便,例如调试,getattr()。然而命名转换的算法有很好的文档说明并且很好操作。
注意3:不是所有人都喜欢命名转换。尽量避免意外的名字冲突和潜在的高级调用。
Public and internal interfaces 公共和内部的接口
任何向后兼容保证只适用于公共接口,因此,用户清晰地区分公共接口和内部接口非常重要。
文档化的接口被认为是公开的,除非文档明确声明它们是临时或内部接口,不受通常的向后兼容性保证。所有未记录的接口都应该是内部的。
为了更好地支持内省(introspection),模块应该使用__all__属性显式地在它们的公共API中声明名称。将__all__设置为空列表表示模块没有公共API。
即使通过__all__设置过,内部接口(包,模块,类,方法,属性或其他名字)依然需要单个下划线前缀。
如果一个命名空间(包,模块,类)被认为是内部的,那么包含它的接口也应该被认为是内部的。
导入的名称应该始终被视作是一个实现的细节。其他模块必须不能间接访问这样的名称,除非它是包含它的模块中有明确的文档说明的API,例如 os.path 或者是一个包里从子模块公开函数接口的 init 模块。
Programming Recommendations 编程建议
代码应该用不损害其他Python实现的方式去编写(PyPy,Jython,IronPython,Cython,Psyco 等)。
比如,不要依赖于在CPython中高效的内置字符连接语句 a += b 或者 a = a + b。这种优化甚至在CPython中都是脆弱的(它只适用于某些类型)并且没有出现在不使用引用计数的实现中。在性能要求比较高的库中,可以种 ”.join() 代替。这可以确保字符关联在不同的实现中都可以以线性时间发生。
和像None这样的单例对象进行比较的时候应该始终用 is 或者 is not,永远不要用等号运算符。
if使用 is not 运算符,而不是 not … is 。
# Correct:
if foo is not None:
# Wrong:
if not foo is None:
当使用富比较(rich comparisons,一种复杂的对象间比较的新机制,允许返回值不为-1,0,1)实现排序操作的时候,最好实现全部的六个操作符(eq, ne, lt, gt, ge)而不是依靠其他的代码去实现特定的比较。
为了最大程度减少这一过程的开销, functools.total_ordering() 修饰符提供了用于生成缺少的比较方法的工具。
始终使用def表达式,而不是通过赋值语句将lambda表达式绑定到一个变量上。
# Correct:
def f(x): return 2*x
# Wrong:
f = lambda x: 2*x
第一个形式意味着生成的函数对象的名称是“f”而不是泛型“< lambda >”。这在回溯和字符串显示的时候更有用。
从Exception继承异常,而不是BaseException。
设计异常的等级,要基于扑捉异常代码的需要,而不是异常抛出的位置。以编程的方式去回答“出了什么问题?”,而不是只是确认“出现了问题”(内置异常结构的例子参考 PEP 3151 )
适当地使用异常链接。在Python 3里,为了不丢失原始的根源,可以显式指定“raise X from Y”作为替代。
当故意替换一个内部异常时(Python 2 使用“raise X”, Python 3.3 之后 使用 “raise X from None”),确保相关的细节转移到新的异常中(比如把AttributeError转为KeyError的时候保留属性名,或者将原始异常信息的文本内容内嵌到新的异常中)。
使用 rasie ValueError(‘message’) 而不是用老的形式 raise ValueError, ‘message’。
当捕获到异常时,如果可以的话写上具体的异常名,而不是只用一个except: 块。
try:
import platform_specific_module
except ImportError:
platform_specific_module = None
如果只有一个except: 块将会捕获到SystemExit和KeyboardInterrupt异常,这样会很难通过Control-C中断程序,而且会掩盖掉其他问题。如果你想捕获所有指示程序出错的异常,使用 except Exception: (只有except等价于 except BaseException:)。
举例两种情况不应该只使用‘excpet’块:
如果异常处理的代码会打印或者记录log;至少让用户知道发生了一个错误。
如果代码需要做清理工作,使用 raise…try…finally 能很好处理这种情况并且能让异常继续上浮。
当给捕捉的异常绑定一个名字时,推荐使用在Python 2.6中加入的显式命名绑定语法:
try:
process_data()
except Exception as exc:
raise DataProcessingFailedError(str(exc))
当捕捉操作系统的错误时,推荐使用Python 3.3 中errno内定数值指定的异常等级。
另外,对于所有的 try/except 语句块,在try语句中只填充必要的代码,这样能避免掩盖掉bug。
# 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)
使用字符串方法代替字符串模块。
字符串方法总是更快,并且和unicode字符串分享相同的API。如果需要兼容Python2.0之前的版本可以不用考虑这个规则。
使用 ”.startswith() 和 ”.endswith() 代替通过字符串切割的方法去检查前缀和后缀。
startswith()和endswith()更干净,出错几率更小。
# Correct:
if foo.startswith('bar'):
pass
# Wrong:
if foo[:3] == 'bar':
pass
对象类型的比较应该用isinstance()而不是直接比较type。
# Correct:
if isinstance(obj, int):
# Wrong:
if type(obj) is type(1):
对于序列来说(strings,lists,tuples),可以使用空序列为false的情况。
# Correct:
if not seq:
if seq:
不要用 == 和True或者False比较:
# Correct:
if greeting:
# Wrong:
if greeting == True:
# Worse:
if greeting is True:
Function Annotations 功能注释
随着PEP 484的引入,功能型注释的风格规范有些变化。
为了向前兼容,在Python3代码中的功能注释应该使用 PEP 484的语法规则。
Python的标准库代码应该保守使用这种注释,但新的代码或者大型的重构可以使用这种注释。
如果代码希望对功能注释有不同的用途,建议在文件的顶部增加一个这种形式的注释:
# type: ignore
这会告诉检查器忽略所有的注释。
像linters一样,类型检测器是可选的可独立的工具。默认情况下,Python解释器不应该因为类型检查而发出任何消息,也不应该基于注释改变它们的行为。
reference: https://blog.youkuaiyun.com/ratsniper/article/details/78954852