文章目录
-
- PEP8 编码风格
-
- 引言
- 代码布局(Code Lay-Out)
-
- 缩进(Indentation)
- 每行最大长度(Maximum Line Length)
- 二元运算符之前还是之后换行 ?(Should a line break before or after a binary operator?)
- 空行(Blank Line)
- 源文件编码
- 模块引用
- 模块级的双下划线命名(Module level funder names)
- 字符串引用(String Quotes)
- 表达式和语句中的空格(Whitespace In Expressions And Statements)
- 注释(Comments)
- 块注释(Block Comments)
- 文档字符串(Documentation Strings)
- 命名约定(Naming Conventions)
-
- 首要原则(Overriding Principle)
- 描述:命名风格(Descriptive: Naming Styles)
- 规范:命名约定(Prescriptive: Naming Conventions)
-
- 需要避免的命名(Names To Avoid)
- ASCII兼容性(ASCII Compatibility)
- 包和模块命名(Package And Module Names)
- 类命名(Class Names)
- 类型变量命名(Type variable names)
- 异常命名(Exception Names)
- 全局变量命名(Global Variable Names)
- 函数命名(Function Names)
- 方法命名和实例变量(Method Names And Instance Variables)
- 常量(Constants)
- 继承的设计(Designing For Inheritance)
- 编程建议(Programming Recommendations)
PEP8 编码风格
引言
A Follish Consistency Is The Hobgoblin Of Little Minds
保持盲目的一致时头脑简单的表现
Guido 的一个重要观点就是代码被读的次数远多于被写的次数
代码布局(Code Lay-Out)
缩进(Indentation)
每个缩进级别采用 4 个空格
连续行的缩进:
- Python 隐式续行,即垂直对齐于圆括号、花括号和方括号
- 悬挂缩进(hanging indent),第一行不应该包括参数,并且在续行中需要再缩进一个级别
每行最大长度(Maximum Line Length)
将所有行都限制在 79 个字符长度以内
确保续行的缩进适当
二元运算符之前还是之后换行 ?(Should a line break before or after a binary operator?)
于二元运算符之前换行
income = (gross_wages
+ tax_interest
+ ...)
空行(Blank Line)
建议使用 2 个空行分隔最外层的函数(function)和类(class)定义
使用 1 个空行来分隔类中的方法(method)定义
在函数内可以使用适量的空行来分隔代码逻辑
源文件编码
Python 核心发行版中的代码应该一直使用 UTF-8
模块引用
Imports 应该分行写
import os
import sys
Imports 应该按照下面的顺序分组来写
- 标准库 imports
- 相关第三方库 imports
- 本地应用/库的特定 imports
不同组的 imports 之前用空格隔开
- 推荐使用绝对 imports,因为这样通常更加易读
import mypkg import sibling from mypkg.sibling import example
标准代码库应当一直使用绝对 imports,避免复杂的包布局
from foo.bar.yourclass import YourClass
- 避免使用通配符 imports,from import *
模块级的双下划线命名(Module level funder names)
模块级的“双下划线”(变量名以两个下划线开头,两个下划线结尾)变量,比如
__all__
,
__author
Python 要求模块中
__future__
的导入必须出现在除文档字符串(docstring)之外的任何其它代码之前
"""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)
- 对于单引号和双引号,最好选择一种使用,避免混淆。
- 当使用三引号表示的字符串,使用双引号字符来表示,这样可以和PEP 257 的文档字符串规则保持一致
def func(): """describe the func of this def""" print('test')
表达式和语句中的空格(Whitespace In Expressions And Statements)
- 方括号,圆括号和花括号之后:
#正确的例子: spam(ham[1], {eggs: 2}) #错误的例子: spam( ham[ 1 ], { eggs: 2 } )
- 逗号,分号或冒号之前:
#正确的例子: if x == 4: print x, y; x, y = y, x #错误的例子: if x == 4 : print x , y ; x , y = y , x
- 不过,在切片操作时,冒号和二元运算符是一样的,应该在其左右两边保留相同数量的空格(就像对待优先级最低的运算符一样)。在扩展切片操作中,所有冒号的左右两边空格数都应该相等。不过也有例外,当切片操作中的参数被省略时,应该也忽略空格。
- 避免任何行末的空格
- 在二元运算符的两边都使用一个空格:赋值运算符(
),增量赋值运算符(=
,+=
etc.),比较运算符(-=
,==
,<
,>
,!=
,<>
,<=
,>=
,in
,not in
,is
),布尔运算符(is not
,and
,or
)。not
- 如果使用了优先级不同的运算符,则在优先级较低的操作符周围增加空白。请你自行判断,不过永远不要用超过1个空格,永远保持二元运算符两侧的空白数量一样。
-
#正确的例子: i = i + 1 submitted += 1 x = x*2 - 1 hypot2 = x*x + y*y c = (a+b) * (a-b) #错误的例子: i=i+1 submitted +=1 x = x * 2 - 1 hypot2 = x * x + y * y c = (a + b) * (a - b)
- 使用
表示关键字参数或参数默认值时,不要在其周围使用空格。=
def complex(real, imag=0.0): return magic(r=real, i=imag)
- 函数注解中的:也遵循:加空格的规则,在 -> 两侧各加一个空格
# true
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
- 有时也可以将短小的if/for/while中的语句写在一行,但对于有多个分句的语句永远不要这样做。也要避免将多行都写在一起。
#最好不要这样: if foo == 'blah': do_blah_thing() for x in lst: total += x while t < 10: t = delay() #绝对不要这样: 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)
末尾逗号通常是可选的,除非在定义单元素元组(tuple)时是必需的(而且在Python 2中,它们具有print语句的语义)。为了清楚起见,建议使用括号(技术上来说是冗余的)括起来。
#正确的例子:
FILES = ('setup.cfg',)
#也正确,但令人困惑:
FILES = 'setup.cfg',
当使用版本控制系统时,在将来有可能扩展的列表末尾添加冗余的逗号是有好处的。具体的做法是将每一个元素写在单独的一行,并在行尾添加逗号,右括号单独占一行。但是,与有括号在同一行的末尾元素后面加逗号是没有意义的(上述的单元素元组除外)。
#正确的例子:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
#错误的例子:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
注释(Comments)
- 和代码矛盾的注释还不如没有
- 当有代码改动时,一定优先更改注释使其保持最新
- 注释应该是完整的多个句子。如果注释是一个短句或一个句子,其首字母应该大写,除非开头是一个以小写字母开头的标识符
- 使用英文写作
- 如果注释很短,结束的句号可以被忽略。且在句尾的句号后再加两个空格
块注释(Block Comments)
块注释一般写在对应代码之前,并且和对应代码有同样的缩进级别。
文档字符串(Documentation Strings)
要知道如何写出好的文档字符串,请参考 PEP 257
- 所有的公共模块,函数,类和方法都应该有文档字符串。
- 对于非公共方法,文档字符串不是必要的,但是你应该留有注释说明该方法的功能,该注释应当出现在 def 的下一行
- PEP 257 描述了好的文档字符应该遵循的规则。其中,最重要的是,多行文档字符串以单行 “”" 结尾,不能有其它字符:
"""Return a foobang Optional plotz says to frobnicate the bizbaz first. """
- 对于仅有一行的文档字符串,结尾处的 “”" 应该也写在这一行
命名约定(Naming Conventions)
Python标准库的命名约定有一些混乱,因此我们永远都无法保持一致。但如今仍然存在一些推荐的命名标准。新的模块和包(包括第三方框架)应该采用这些标准,但若是已经存在的包有另一套风格的话,还是应当与原有的风格保持内部一致。
首要原则(Overriding Principle)
对于用户可见的公共部分API,其命名应当表达出功能用途而不是其具体的实现细节。
描述:命名风格(Descriptive: Naming Styles)
存在很多不同的命名风格,最好能够独立地从命名对象的用途认出采用了哪种命名风格。
通常区分以下命名样式:
-
(单个小写字母)b
-
(单个大写字母)B
-
(小写)lowercase
-
(带下划线小写)lower_case_with_underscores
-
(大写)UPPERCASE
-
(带下划线大写)UPPER_CASE_WITH_UNDERSCORES
-
CapitalizedWords
(也叫做CapWords或者CamelCase – 因为单词首字母大写看起来很像驼峰)。也被称作StudlyCaps。
注意:当CapWords里包含缩写时,将缩写部分的字母都大写。
比HTTPServerError
要好。HttpServerError
-
(注意:和CapitalizedWords不同在于其首字母小写!)mixedCase
-
(这种风格超丑!)Capitalized_Words_With_Underscores
也有风格使用简短唯一的前缀来表示一组相关的命名。这在Python中并不常见,但为了完整起见这里也捎带提一下。比如,
os.stat()
函数返回一个tuple,其中的元素名原本为
st_mode
,
st-size
,
st_mtime
等等。(这样做是为了强调和POSIX系统调用结构之间的关系,可以让程序员更熟悉。)
X11库中的公共函数名都以X开头。在Python中这样的风格一般被认为是不必要的,因为属性和方法名之前已经有了对象名的前缀,而函数名前也有了模块名的前缀。
此外,要区别以下划线开始或结尾的特殊形式(可以和其它的规则结合起来):
-
: 以单个下划线开头是”内部使用”的弱标志。 比如,_single_leading_underscore
不会import下划线开头的对象。from M import *
-
: 以单个下划线结尾用来避免和Python关键词产生冲突,例如:single_trailing_underscore_
Tkinter.Toplevel(master, class_='ClassName')
-
: 以双下划线开头的风格命名类属性表示触发命名修饰(在FooBar类中,__double_leading_underscore
命名会被修饰成__boo
; 见下)。_FooBar__boo
-
: 以双下划线开头和结尾的命名风格表示“魔术”对象或属性,存在于用户控制的命名空间(user-controlled namespaces)里(也就是说,这些命名已经存在,但通常需要用户覆写以实现用户所需要的功能)。 比如,__double_leading_and_trailing_underscore__
,__init__
或__import__
。请依照文档描述来使用这些命名,千万不要自己发明。__file__
规范:命名约定(Prescriptive: Naming Conventions)
需要避免的命名(Names To Avoid)
不要使用字符’l’(L的小写的字母),’O’(o大写的字母),或者’I’(i的大写的字母)来作为单个字符的变量名。
在一些字体中,这些字符和数字1和0无法区别开来。比如,当想使用’l’时,使用’L’代替。
ASCII兼容性(ASCII Compatibility)
标准库中使用的标识符必须与ASCII兼容(参见PEP 3131中的policy这一节) 。
包和模块命名(Package And Module Names)
模块命名应短小,且为全小写。若下划线能提高可读性,也可以在模块名中使用。
Python 包命名也应该短小,且为全小写,但不应该使用下划线
类命名(Class Names)
类命名应该使用驼峰命名法
当接口已有文档说明且主要是被用作调用时,也可以使用函数的命名约定。
注意对于内建命名(builtin names)有一个特殊的约定:大部分内建名都是一个单词(或者两个一起使用的单词),驼峰(CapWords)的约定只对异常命名和内建常量使用。
类型变量命名(Type variable names)
PEP 484中引入的类型变量名称通常应使用简短的驼峰命名:
T
,
AnyStr
,
Num
。 建议将后缀
_co
或
_contra
添加到用于声明相应的协变(covariant)和逆变(contravariant)的行为。例如:
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__
机制来避免import全局变量,或者采用下划线前缀的旧约定来命名全局变量,从而表明这些变量是“模块非公开的”。
函数命名(Function Names)
函数命名应该都是小写,必要时使用下划线来提高可读性。
只有当已有代码风格已经是混合大小写时(比如threading.py),为了保留向后兼容性才使用混合大小写。
方法命名和实例变量(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,哪些真的只会被你的基类调用。
请记住以上几点,下面是Python风格的指南:
- 公开属性不应该有开头下划线。
-
如果公开属性的名字和保留关键字有冲突,在你的属性名尾部加上一个下划线。这比采用缩写和简写更好。(然而,和这条规则冲突的是,‘cls’对任何变量和参数来说都是一个更好地拼写,因为大家都知道这表示class,特别是在类方法的第一个参数里。)
注意 1:对于类方法,参考之前的参数命名建议。
- 对于简单的公共数据属性,最后仅公开属性名字,不要公开复杂的调用或设值方法。请记住,如果你发现一个简单的数据属性需要增加功能行为时,Python为功能增强提供了一个简单的途径。这种情况下,使用
Properties
注解将功能实现隐藏在简单数据属性访问语法之后。
注意 1:
Properties
注解仅仅对新风格类有用。
注意 2:尽量保证功能行为没有副作用,尽管缓存这种副作用看上去并没有什么大问题。
注意 3: 对计算量大的运算避免试用properties;属性的注解会让调用者相信访问的运算量是相对较小的。
-
如果你的类将被子类继承的话,你有一些属性并不想让子类访问,考虑将他们命名为两个下划线开头并且结尾处没有下划线。这样会触发Python命名修饰算法,类名会被修饰添加到属性名中。这样可以避免属性命名冲突,以免子类会不经意间包含相同的命名。
注意 1:注意命名修饰仅仅是简单地将类名加入到修饰名中,所以如果子类有相同的类名合属性名,你可能仍然会遇到命名冲突问题。
注意 2:命名修饰可以有特定用途,比如在调试时,
__getattr__()
比较不方便。然而命名修饰算法的可以很好地记录,并且容意手动执行。
注意 3:不是所有人都喜欢命名修饰。需要试着去平衡避免偶然命名冲突的需求和高级调用者使用的潜在可能性。
编程建议(Programming Recommendations)
-
代码应该以不影响其他Python实现(PyPy,Jython,IronPython,Cython,Psyco等)的方式编写。
例如,不要依赖于 CPython 在字符串拼接时的优化实现,像这种语句形式
和a += b
。即使是 CPython(仅对某些类型起作用) 这种优化也是脆弱的,不是在所有的实现中都不使用引用计数。在库中性能敏感的部分,用a = a + b
形式来代替。这会确保在所有不同的实现中字符串拼接是线性时间的。''.join
- 与单例作比较,像
应该用None
或is
,从不使用is not
==
操作符。
同样的,当心
这样的写法,你是不知真的要判断if x is not None
不是x
。例如,测试一个默认值为None
的变量或参数是否设置成了其它值,其它值有可能是某种特殊类型(如容器),这种特殊类型在逻辑运算时其值会被当作None
来看待。Flase
- 用
操作符而不是is not
。虽然这两个表达式是功能相同的,前一个是更可读的,是首选。not ... is
- 捕获异常时,尽可能使用明确的异常,而不是用一个空的
except:
语句
例如,用:
try: import platform_specific_module except ImportError: platform_specific_module = None
- 另外,对于所有
/try
子句,将except
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
-
当它们做一些除了获取和释放资源之外的事的时候,上下文管理器应该通过单独的函数或方法调用。例如:
推荐的写法:
不推荐的写法:with conn.begin_transaction(): do_stuff_in_transaction(conn)
with conn: do_stuff_in_transaction(conn)
- 坚持使用
语句。函数内的return
语句都应该返回一个表达式,或者return
。如果一个None
语句返回一个表达式,另一个没有返回值的应该用return
清晰的说明,并且在一个函数的结尾应该明确使用一个return None
return
语句(如果有返回值的话)。
推荐的写法:
不推荐的写法: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)
def foo(x): if x >= 0: return math.sqrt(x) def bar(x): if x < 0: return return math.sqrt(x)
-
用字符串方法代替字符串模块。
字符串方法总是快得多,并且与unicode字符串共享相同的API。如果需要与2.0以下的Python的向后兼容,则覆盖此规则。
- 用
和''.startswith()
代替字符串切片来检查前缀和后缀。''.endswith()
和startswith()
是更简洁的,不容易出错的。例如:endswith()
#推荐的写法: if foo.startswith('bar'): #不推荐的写法: if foo[:3] == 'bar':
- 对象类型的比较应该始终使用
而不是直接比较。isinstance()
当比较一个对象是不是字符串时,记住它有可能也是一个 unicode 字符串!在 Python 2 里面,#推荐的写法: if isinstance(obj, int): #不推荐的写法: if type(obj) is type(1):
和str
有一个公共的基类叫unicode
,因此你可以这样做:basestring
注意,在 Python 3 里面,if isinstance(obj, basestring):
和unicode
已经不存在了(只有basestring
),str
对象不再是字符串的一种(被一个整数序列替代)。byte
- 不要让字符串对尾随的空格有依赖。这样的尾随空格是视觉上无法区分的,一些编辑器(或者,reindent.py)会将其裁剪掉。
- 不要用
比较==
和True
。False
#推荐的写法: if greeting: #不推荐的写法: if greeting == True: #更加不推荐的写法: if greeting is True: