(1)本文提供的Python代码编码规范基于Python主要发行版本的标准库。
(2)本文以及PEP 257(文档字符串的规范)改编自Guido原始的《Python Style Guide》一文,同时添加了一些来自Barry的风格指南2
(3)文档中的约定应基于现实考虑灵活处理,根据自身需求和团队其他成员的阅读习惯做适当调整。
(4)若遵循文档中的规范使得代码可读性更差,或者因不符合个人习惯会造成代码错误,应适当采用。
(5)已完成的代码未很好的遵循文档规范,不建议重新修改。
(6)本文提供的规范指南随着时间的推移而逐渐演变,随着语言本身的变化,过去的约定也被淘汰了;
许多项目有自己的编码规范,在出现规范冲突时,项目自身的规范优先。
(7)不要为了遵守PEP约定而破坏兼容性!
每一级缩进使用4个空格。
续行应该与其包裹元素对齐,要么使用圆括号、方括号和花括号内的隐式行连接来垂直对齐,要么使用挂行缩进对齐。
当使用挂行缩进时,应该考虑到第一行不应该有参数,以及使用缩进以区分自己是续行。
推荐:
# 用更多的缩进来与其他行区分
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# 挂行缩进应该再换一行
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)
# 没有使用垂直对齐时,禁止把参数放在第一行
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):
# Since both conditions are true, we can frobnicate.
do_something()
# 在条件判断的语句添加额外的缩进
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',
)
(1)首选的缩进方式应选择空格。
(2)制表符只能用于与同样使用制表符缩进的代码保持一致。
(3)Python3不允许混合使用空格和制表符。
(4)Python2开发的项目代码中应将混合使用空格和制表符的内容统一修改为空格。
(5)用命令行调用Python2项目代码时,建议加入-t或者-tt选项。加入-t会在混合使用制表符和空格的位置发出警告,加入-tt这些警告会变成错误,给予提示。
(1)所有行限制的最大字符数为79个字符。
(2)对于结构限制较少的长文本块(文档字符或者注释),行长度应限制为72个字符。
(3)限制所需的编辑器窗口宽度(控制行长度)可以使多个文件并排打开,并且在相邻列中显示两个版本的代码审阅工具时效果更好。
(4)如果代码主要由一个团队维护,且同意使用更长的行宽,则可以把长度从80增加到100(或99),但是注释和文档字符仍然应保持72个字符换行。
(5)Python标准库相对保守,需要将行宽限制在79个字符(文档字符和注释限制在72个字符)。
(6)较长行的代码首选的换行方式是在括号、方括号和大括号内使用Python的隐式换行,不应优先使用反斜杠来换行。
(7)多个with(此外还有:long、multiple等)状态语句下句子较长,不能使用隐式换行,应使用反斜杠换行:
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
- student_loan_interest)
不推荐:
# 不推荐: 操作符离操作数太远
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
(1)顶层函数和类的定义,前后应用两个空行隔开。
(2)类里的方法定义用一个空行隔开。
(3)相关的功能组可以用额外的空行隔开,相关的单行代码之间的空白行可以省略。
(4)应尽量避免在函数中使用空行来区分逻辑段。
(1)Python核心发布版本中的代码总是以UTF-8格式编码(或者在Python2中用ASCII编码)。
(2)标准库中非默认的编码(UTF-8和ASCII)应只用于测试。注释或文档字符串中需要出现含ASCII字符编码的内容时,应该使用\x,\u,\U , 或者 \N 进行转义。
(3)使用ASCII(Python2中)或UTF-8(Python3中)编码的文件不应具有编码声明(# -- coding: UTF-8 –-)。
(4)Python3和更高版本标准库规定:所有标识符必须使用ASCII标识符,并在可行的情况下使用英语单词。此外,字符串文字和注释也必须是ASCII编码格式。唯一的例外是(a)测试非ASCII特征的测试用例,以及(b)作者的名称。作者的名字如果不使用拉丁字母拼写,必须提供一个拉丁字母的音译。
(1)导入通常应逐行进行
推荐:
import os
import sys
不推荐:
import sys, os
但是可以这样:
from subprocess import Popen, PIPE
(2)导入总是位于文件的顶部,在模块注释和文档字符串之后,在模块的全局变量与常量之前。
导入应该按照以下顺序分组:
标准库导入
相关第三方库导入
本地应用/库特定导入 且应该在每一组导入之间加入空行。
(3)为提高可读性,应使用绝对路径导入:
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
在绝对路径过于冗长时也可使用显式的相对路径进行导入:
from . import sibling
from .sibling import example
(4)当从一个包含类的模块中导入类时,通常应该写成如下格式:
from myclass import MyClass
from foo.bar.yourclass import YourClass
如果上述的写法导致名字的冲突,那么应改为如下方式:
import myclass
import foo.bar.yourclass
然后使用“myclass.MyClass”和“foo.bar.yourclass.YourClass”。
(5)应极力避免使用通配符的方式导入(from import *)。
dunder指的是像__all__ , __author__ , __version__ 等这样的模块级名称。dunder应该放在文档字符串的后面,以及除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
(1)在Python中,单引号和双引号字符串是相同的。当一个字符串中包含单引号或者双引号字符的时候,使用和最外层不同的符号来避免使用反斜杠,从而提高可读性。
(2)推荐在一个函数或者代码块中保持使用风格一致。
(1)括号内[小括号|中括号|大括号]不要有空格。
Yes: spam(ham[1], {eggs: 2})
No: spam( ham[ 1 ], { eggs: 2 } )
(2)紧贴在逗号、分号或者冒号之前(不要在逗号, 分号, 冒号前面加空格, 但应该在它们后面加(除了在行尾))。
Yes: if x == 4: print x, y; x, y = y, x
No: if x == 4 : print x , y ; x , y = y , x
(3)冒号在切片中类似于二元运算符,在两边应该有相同数量的空格。例外情况:当一个切片参数被省略时,空格就被省略。
推荐写法:
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]
不推荐写法:
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
(4)紧贴在函数参数的左括号之前。
Yes: spam(1)
No: spam (1)
(5)紧贴索引或者切片的左括号之前。
Yes: dct['key'] = lst[index]
No: dct ['key'] = lst [index]
(6)为了和另一个赋值语句对齐,在赋值运算符附件加多个空格。
推荐:
x = 1
y = 2
long_variable = 3
不推荐:
x = 1
y = 2
long_variable = 3
(1)避免在尾部添加空格。因为尾部的空格通常都看不见,会产生混乱:比如,一个反斜杠后面跟一个空格的换行符,不算续行标记。
(2)总是在二元运算符两边加一个空格。
# 赋值(=),
# 增量赋值(+=,-=),
# 比较(==,<,>,!=,<>,<=,>=,in,not,in,is,is not),
# 布尔(and, or, not)
(3)如果使用具有不同优先级的运算符,请考虑在具有最低优先级的运算符周围添加空格。但是,不要使用一个以上的空格,并且在二元运算符的两边使用相同数量的空格。
推荐:
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)
(4)在制定关键字参数或者默认参数值的时候,不要在=附近加上空格。
推荐:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
不推荐:
def complex(real, imag = 0.0):
return magic(r = real, i = imag)
(5)功能型注释应该使用冒号的一般性规则,并且在使用->的时候要在两边加空格。
推荐:
def munge(input: AnyStr): ...
def munge() -> AnyStr: ...
不推荐:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
(6)当给有类型备注的参数赋值的时候,在=两边添加空格(仅针对那种有类型备注和默认值的参数)。
推荐:
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
不推荐:
def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...
(7)复合语句(同一行中的多个语句)通常是不允许以空格分隔的。
推荐:
if foo == 'blah':
do_blah_thing()
do_one()
do_two()
do_three()
不推荐:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
(8)有多行语句块时不要将小的代码块和 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()
(1)错误的注释不如没有注释!
(2)代码更改前,优先更新对应的注释!
(3)注释应该是完整的句子。如果注释是英文短语或句子,它的第一个单词应该大写,除非它是以小写字母开头的标识符。
(4)块注释一般由完整句子的一个或多个段落组成,并且每句话结束应有个句号。
(1)块注释通常适用于跟随它们的某些(或全部)代码,并缩进到与代码相同的级别。通常块注释的每一行开头使用一个#和一个空格。
(2)块注释内部的段落通过只有一个#的空行分隔。
行内注释是与代码语句同行的注释。行内注释和代码至少要有两个空格分隔。注释由#和一个空格开始。事实上,如果状态明显的话,行内注释一般是不必要的,
不需要的写法:
x = x + 1 # 增量x
需要的写法:
x = x + 1 # 边界补偿
(1)要为所有的公共模块,函数,类以及方法编写文档说明。
(2)多行文档说明使用的结尾三引号应该自成一行,例如:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
对于单行的文档说明,尾部的三引号应该和文档在同一行。
TODO注释应该在所有开头处包含”TODO”字符串, 紧跟着是用括号括起来的你的名字, email地址或其它标识符. 然后是一个可选的冒号. 接着必须有一行注释, 解释要做什么. 主要目的是为了有一个统一的TODO格式, 这样添加注释的人就可以搜索到(并可以按需提供更多细节). 写了TODO注释并不保证写的人会亲自解决问题. 当你写了一个TODO, 请注上你的名字.
# TODO([email protected]): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.
函数注释
def update_lun(flag, lun_id, param):
"""
更新卷
:param flag: name:重命名卷; size:调整卷容量; priority:设置卷优先级; qos:设置卷qos
:param lun_id: 逻辑池id
:param param: 根据flag类型进行参数传递
name:卷名称;
size:卷大小(1MB-256TB((1*1024^2~256*1024^4)),新容量不可小于或等于原容量的大小);
priority:卷优先级(normal或high);
qos:字典形式
read_bps 读带宽qos 否 int, string 2097152(2MB)-10^18,0表示不限制
write_bps 写带宽qos 否 int, string 2097152(2MB)-10^18,0表示不限制
write_iops 写IOPS 否 int, string 1000-10^18,0表示不限制
:return:
{'status_code': status_code, 'result': result}
"""
pass
那些给用户的API接口的命名,应该遵循反映使用场景而不是实现的原则(例如‘剪切’功能)。
常见的命名方式:
b(单个小写字母)
B(单个大写字母)
lowercase(小写字母)
lower_case_with_underscores (使用下划线分隔的小写字母,写的最多)
UPPERCASE (大写字母)
UPPER_CASE_WITH_UNDERSCORES (使用下划线分隔的大写字母)
CapitalizedWords(驼峰命名法) 注意:当在首字母大写的风格中用到缩写时,所有缩写的字母用大写,因此,HTTPServerError 比 HttpServerError 好。
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__。除了作为文档之外,要避免这样的名。
(1)应避免的名字
(2)包名和模块名
(3)类名
(4)异常名
(5)全局变量名
(6)函数名
(7)函数和方法参数
始终要将 self 作为实例方法的的第一个参数。
始终要将 cls 作为类静态方法的第一个参数。
如果函数的参数名和已有的关键词冲突,在最后加单一下划线比缩写或随意拼写更好。因此 class_ 比 clss 更好。(也许最好用同义词来避免这种冲突)
(8)方法名和实例变量
遵循如下函数命名规则:使用下划线分隔小写单词以提高可读性。
在非共有方法和实例变量前使用单下划线。
通过双下划线前缀触发Python的命名转换规则来避免和子类的命名冲突。
(9)常量
(1)考虑到一个类的方法和实例变量(统称:属性)应该是共有还是非共有。如果存在疑问,那就选非共有;因为将一个非共有变量转为共有比反过来更容易。
(2)公共属性是那些与类无关的而使用的属性,并承诺避免向后不兼容的更改。非共有属性是那些不打算让第三方使用的属性;你不需要承诺非共有属性不会被修改或被删除。
(3)不使用“私有(private)”这个说法,是因为在Python中目前还没有真正的私有属性(为了避免大量不必要的常规工作)。
(4)另一种属性作为子类API的一部分(在其他语言中通常被称为“protected”)。有些类是专为继承设计的,用来扩展或者修改类的一部分行为。当设计这样的类时,要谨慎决定哪些属性时公开的,哪些是作为子类的API,哪些只能在基类中使用
贯彻这样的思想,一下是一些让代码Pythonic的准则:
(1)代码应该用不损害其他Python实现的方式去编写。
比如,不要依赖于在CPython中高效的内置字符连接语句 a += b 或者 a = a + b。在性能要求比较高的库中,可以种 ”.join() 代替。这可以确保字符关联在不同的实现中都可以以线性时间发生。
(2)和像None这样的对象进行比较的时候应该始终用 is 或者 is not,永远不要用等号运算符。
另外,如果你在写 if x 的时候,应注意你是否表达的意思是 if x is not None。
(3)使用 is not 运算符,而不是 not … is 。虽然这两种表达式在功能上完全相同,但前者在逻辑上更易于阅读,所以优先考虑。
推荐:
if foo is not None:
不推荐:
if not foo is None:
(4)当使用富比较(rich comparisons,一种复杂的对象间比较的新机制,允许返回值不为-1,0,1)实现排序操作的时候,最好实现全部的六个操作符(__eq__, __ne__, __lt__, __gt__, __ge__,__le__)而不是依靠其他的代码去实现特定的比较。
(5)始终使用def表达式,而不是通过赋值语句将lambda表达式绑定到一个变量上。
推荐:
def f(x): return 2*x
不推荐:
f = lambda x: 2*x
第一个形式意味着生成的函数对象的名称是“f”而不是泛型“< lambda >”。这在回溯和字符串显示的时候更有用。
(6)从Exception继承异常,而不是BaseException。直接继承BaseException的异常适用于几乎不用来捕捉的异常。(BaseException 派生的异常是: GeneratorExit , KeyboardInterrupt , SystemExit )
(7)设计异常的等级,要基于捕捉异常代码的需要,而不是异常抛出的位置。以编程的方式去回答“出了什么问题?”,而不是只是确认“出现了问题”。(某处需要捕获异常时设计异常捕获,而不是出错以后才去设计捕获)
(8)在处理异常的过程中,类的命名规范同样适用,如果异常是一个Error的话,需要添加一个“Error”的后缀到你的异常类。
(9)当捕获到异常时,如果可以的话写上具体的异常名,而不是只用一个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))
为了避免和原来基于逗号分隔的语法出现歧义,Python3只支持这一种语法。
当捕捉操作系统的错误时,推荐使用Python 3.3 中errno内定数值指定的异常等级。
另外,对于所有的 try/except 语句块,在try语句中只填充必要的代码,这样能避免掩盖掉bug。
推荐:
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)
(10)使用 ”.startswith() 和 ”.endswith() 代替通过字符串切割的方法去检查前缀和后缀。 startswith()和endswith()更干净,出错几率更小。比如:
# 推荐:
if foo.startswith('bar'):
# 糟糕:
if foo[:3] == 'bar':
(11)对象类型的比较应该用isinstance()而不是直接比较type。
# 正确:
if isinstance(obj, int):
# 糟糕:
if type(obj) is type(1):
(12)对于序列来说(strings,lists,tuples),可以使用空序列为false的情况。(逻辑很重要)
# 正确:
if not seq:
if seq:
# 糟糕:
if len(seq):
if not len(seq):
(13)不要用 == 去和True或者False比较:
# 正确:
if greeting:
# 糟糕:
if greeting == True:
# 更糟:
if greeting is True: