Python编码规范(上)

术语定义
规则: 编程时必须遵守的约定

说明: 某个规则的具体解释

建议: 编程时必须加以考虑的约定

错误示例: 违背某条规则的例子

正确示例: 遵循某条规则的例子

例外情况: 相应的规则不适用的场景

信任边界: 位于信任边界之内的所有组件都是被系统本身直接控制的。所有来自不受控的外部系统的连接与数据,包括客户端与第三方系统,都应该被认为是不可信的,要先在边界处对其校验,才能允许它们进一步与本系统交互。

非信任代码: 非产品包中的代码,如通过网络下载到本地虚拟机中加载并执行的代码。

  1. 排版
    缩进
    规则1.1 程序块采用4个空格缩进风格编写
    说明:程序块采用缩进风格编写,缩进的空格数为4个,是业界通用的标准。

错误示例:空格个数不为4个

def load_data(dirname, one_hot=False):
X_train = [] #5个空格
Y_train = [] #5个空格
Copy
正确示例:

def load_data(dirname, one_hot=False):
X_train = []
Y_train = []
Copy
规则1.2 禁止混合使用空格(space)和跳格(Tab)
说明:推荐的缩进方式为仅使用空格(space)。仅使用跳格(Tab)也是允许的。如果已有代码中混合使用了空格及跳格,要全部转换为空格。

错误示例:空格和跳格混合使用

def load_data(dirname, one_hot=False):
X_train = [] #跳格
Y_train = []
Copy
正确示例:

def load_data(dirname, one_hot=False):
X_train = []
Y_train = []
Copy
规则1.3 新项目必须使用纯空格(spaces)来代替跳格(Tab)
说明:对于新项目,必须使用纯空格(spaces)来代替跳格(Tab)。

错误示例:新项目使用跳格

def load_data(dirname, one_hot=False):
X_train = [] #跳格
Y_train = [] #跳格
Copy
正确示例:

def load_data(dirname, one_hot=False):
X_train = []
Y_train = []
Copy
语句
规则1.4 Python文件中必须使用UTF-8编码
说明:Python文件中应该使用UTF-8编码(Python2.x中默认使用ASCII编码), 使用ASCII或UTF-8的文件必须有编码声明. 另外使用\x转义字符是在字符串中包含非ASCII(non-ASCII)数据的首选方法.

规则1.5 一行只写一条语句
说明:不允许把多个短语句写在一行中,即一行只写一条语句。多条语句写在一行,这样做一个很明显得缺点就是在调试的时候无法单步执行。

错误示例:多条语句在一行,不方便单步调试

rect.length = 0; rect.width = 0;
Copy
正确示例:

rect.length = 0
rect.width = 0
Copy
规则1.6 相对独立的程序块之间、变量说明之后必须加空行
说明:相对独立的程序块之间、变量说明之后加上空行,代码可理解性会增强很多。

错误示例:程序块之间未加空行

if len(deviceName) < _MAX_NAME_LEN:
……
writer = LogWriter()
Copy
正确示例:

if len(deviceName) < _MAX_NAME_LEN:
……

writer = LogWriter()
Copy
建议1.7 一行长度小于80个字符,与Python标准库看齐
说明:建议开发团队用本产品线的门禁工具或者yapf(https://github.com/google/yapf) 自动格式化,或者用IDE自带的格式化功能统一格式化代码后再提交。

较长的语句、表达式或参数(>80字符)要分成多行书写,首选使用括号(包括{},[],())内的行延续,推荐使用反斜杠(\)进行断行。长表达式要在低优先级操作符处划分新行,操作符统一放在新行行首或原行行尾,划分出的新行要进行适当的缩进,使排版整齐,语句可读。

错误示例:一行字符太多,阅读代码不方便

if width == 0 and height == 0 and color == ‘red’ and emphasis == ‘strong’ and
highlight > 100:
x = 1
Copy
正确示例:

if width == 0
and height == 0
and color == ‘red’
and emphasis == ‘strong’
and highlight > 100:
x = 1
Copy
空格
规则1.8 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符前后要加空格
说明:采用这种松散方式编写代码的目的是使代码更加清晰。

在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留一个以上空格。

1、逗号、分号(假如用到的话)只在后面加空格。

错误示例:

print a,b , c
Copy
正确示例:

print a, b, c
Copy
2、比较操作符">"、">="、"<"、"=<"、"==", 赋值操作符"="、"+=",算术操作符"+"、"-"、"%",逻辑操作符and, or等双目操作符的前后加空格。

错误示例:

if current_time>= MAX_TIME_VALUE:
a=b+ c
a+=2
Copy
正确示例:

if current_time >= MAX_TIME_VALUE:
a = b + c
a += 2
Copy
建议1.9 进行非对等操作时,如果是关系密切的立即操作符(如 .),后不应加空格
函数定义语句中的参数默认值,调用函数传递参数时使用的等号,建议不加空格
def create(self, name=None)
self.create(name=”mike”)
Copy
""、"**"等作为操作符时,前后不加空格。 错误示例:
a = b * c
a = c ** b
Copy
正确示例:
a = b
c
a = c**b
Copy
"."前后不加空格。 错误示例:
result. writeLog()
Copy
正确示例:
result.writeLog()
Copy
括号内侧,左括号后面和右括号前面,不需要加空格,多重括号间不必加空格。 错误示例:
a = ( (b + c)*d - 5 )*6
Copy
正确示例:
a = ((b + c)*d - 5)*6
Copy
紧贴索引切片或被调用函数名,开始的括号前,不需要加空格。 错误示例:
Dict [key] = list [index]
conn = Telnet.connect (ipAddress)
Copy
正确示例:
dict[key] = list[index]
conn = Telnet.connect(ipAddress)
Copy
导入
规则1.10 加载模块必须分开每个模块占一行
说明:单独使用一行来加载模块,让程序依赖变得更清晰。

错误示例:

import sys, os
Copy
正确示例:

import sys
import os
Copy
规则1.11 导入部分(imports)置于模块注释和文档字符串之后,模块全局变量和常量声明之前
说明:导入部分(imports)置于模块注释和文档字符串之后,模块全局变量和常量声明之前。导入(import)库时,按照标准库、第三方关联库、本地特定的库/程序顺序导入,并在这几组导入语句之间增加一个空行。

正确示例:

import sys
import os

from oslo_config import cfg
from oslo_log import log as logging

from cinder import context
from cinder import db
Copy
建议1.12 避免使用from xxx import *的方式导入某模块的所有成员。
说明: from xxx import *会将其他模块中的所有成员挨个赋值给当前范围的同名变量,如果当前范围已经有同名变量,则会静默将其覆盖。这种方式容易导致名字冲突,且冲突后不容易定位,应当尽量避免使用。

正确示例: 如果需要使用yyy,则from xxx import yyy

解释器
建议1.13 类Unix操作系统上直接执行的Python文件头部建议使用#!/usr/bin/env python指定解释器
说明:类Unix操作系统上使用Hashbang “#!/usr/bin/env python”声明的时候,会去取系统的 PATH 变量中指定的第一个 Python 来执行你的脚本,有助于正确指定执行Python文件的解释器。Hashbang的位置需要放在文件编码声明之前。 Windows操作系统可忽略此建议。

  1. 注释
    类、接口和函数
    规则2.1 类和接口的注释写在类声明(class ClassName:)所在行的下一行,并向后缩进4个空格
    说明:功能描述除了描述类或接口功能外,还要写明与其他类或接口之间的关系;属性清单列出该类或接口的接口方法的描述;修改记录包括修改人,修改日期及修改内容。

正确示例:

class TreeError(libxmlError):
“”"
功能描述:
接口:
修改记录:
“”"
Copy
规则2.2 公共函数的注释写在函数声明(def FunctionName(self):)所在行的下一行,并向后缩进4个空格
说明:公共函数注释的内容包括功能描述、输入参数、输出参数、返回值、调用关系(函数、表)、异常描述,修改记录等。异常描述必须说明异常的含义及什么条件下抛出该异常,除描述函数内部抛出的异常外。

正确示例:

def load_batch(fpath):
“”"
功能描述:
参数:
返回值:
异常描述:
修改记录
“”"
Copy
属性
规则2.3 公共属性的注释写在属性声明的上方,与声明保持同样的缩进。行内注释应以#和一个空格作为开始,与后面的文字注释以一个空格隔开
说明:行内注释的形式是在语句的上一行中加注释。行内注释要少用。它们应以#和一个空格作为开始。

错误示例:

#Compensate for border
x = x + 1
Copy
正确示例:

Compensate for border

x = x + 1
Copy
格式
规则2.4 模块注释写在文件的顶部,导入(import)部分之前的位置,不需要缩进
说明:每次模块代码修改后要写明修改信息,修改信息包括修改人,修改日期及修改内容。

正确示例:

“”"
功 能:XXX类,该类主要涉及XXX功能
版权信息:华为技术有限公司,版本所有© 2010-2017
修改记录:2015-3-17 12:00 XXX XXXXXXXX 创建
2017-3-17 12:00 XXX XXXXXXXX 修改 XXX
“”"
Copy
规则2.5 文档字符串多于一行时,末尾的""“要自成一行
说明:对于只有一行的文档字符串,把”""放到同一行也没问题。单行可以放同一行。

错误示例:

“”“Return a foobang
Optional plotz says to frobnicate the bizbaz first.”""
Copy
正确示例:

“”“Return a foobang
Optional plotz says to frobnicate the bizbaz first.
“””
Copy
正确示例:

“”“API for interacting with the volume manager.”""
Copy
规则2.6 注释必须与其描述的代码保持同样的缩进,并放在其上方相邻位置
说明:注释应与其描述的代码相近,并与所描述的代码保持同样的缩进。对代码的注释应放在其上方相邻位置,不可放在下面。

错误示例:注释与所描述的代码有不同的缩进

# get replicate sub system index and net indicator

repssn_ind = ssn_data[index].repssn_index
repssn_ni = ssn_data[index].ni
Copy
正确示例:

get replicate sub system index and net indicator

repssn_ind = ssn_data[index].repssn_index
repssn_ni = ssn_data[index].ni
Copy
正确示例:

if image_service is not None:
# Deletes the image if it is in queued or saving state
self._delete_image(context, image_meta[‘id’], image_service)
Copy
建议
建议2.7 源程序有效注释量(包括DocString)应该在20%以上
说明:注释的原则是有助于对程序的阅读理解,没有类型信息,IDE不能帮助提示,如果没有注释,动态语言就很难理解,注释不宜太多也不能太少,注释描述必须准确、易懂、简洁。

建议2.8 注释的内容要清楚,防止注释二义性
说明:错误的注释不但无益反而有害。注释的要点是准确,没有二义性。把代码说清楚是目的。

建议2.9 避免在注释中使用缩写
说明:在使用缩写时或之前,应对缩写进行必要的说明。

建议2.10 保持代码和注释的同步修改
说明:边写代码边注释,修改代码时始终优先更新相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。

建议2.11 有含义的变量应该加上注释
说明:对于有物理含义的变量,如果其命名不是充分自注释的,在声明时必须加以注释,说明其物理含义。变量的注释应放在其上方相邻位置。

错误示例:

没有注释

_MAX_ACT_TASK_NUMBER = 1000
Copy
正确示例:

maximum number of active statistic tasks

_MAX_ACT_TASK_NUMBER = 1000
Copy
建议2.12 全局变量要有较详细的注释
说明:全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程修改它以及存取时注意事项等的说明。

3.命名
包和模块
规则3.1 包(Package)、模块(Module)名使用意义完整的英文描述,采用小写加下划线(lower_with_under)的风格命名
说明:模块应该用小写加下划线的方式(如lower_with_under.py)命名。尽管已经有很多现存的模块使用类似于CapWords.py这样的命名,但现在已经不鼓励这样做, 因为如果模块名碰巧和类名一致, 这会让人困扰。

正确示例:

from sample_package import sample_module
from sample_module import SampleClass
Copy

规则3.2 类(Class)名使用意义完整的英文描述,采用大写字母开头的单词(CapWords)风格命名
说明:类沿用面向对象语言最常用的CapWords风格命名。

正确示例:

class SampleClass(object):
pass
Copy
函数
规则3.3 函数(Function)、方法(Method)、函数参数(Function Parameters)名使用意义完整的英文描述,采用小写加下划线(lower_with_under)的风格命名
说明: 函数、方法采用小写加下划线的风格命名,与类名做区分。 函数参数采用小写加下划线的风格命名,与一般变量的命名风格保持一致。 模块内部使用的函数用单下划线(_)开头,表示函数是protected的(使用from module1 import *时不会包含)。

正确示例:

def sample_public_function(sample_parameter):
pass

def sample_internal_function(sample_parameter):
pass

class SampleClass(object):

def sample_member_method(self, sample_parameter):
    pass

Copy
变量
规则3.4 变量(variable)采用小写加下划线(lower_with_under)的风格命名。常量(constant)采用大写加下划线(CAPS_WITH_UNDER)的风格命名
说明:

常量使用大写加下划线的风格命名,与变量做区分。

正确示例:

sample_global_variable = 0
M_SAMPLE_GLOBAL_CONSTANT = 0

class SampleClass(object):

SAMPLE_CLASS_CONSTANT = 0

def sample_member_methond(self, sample_parameter):
    pass

def sample_function():
sample_function_variable = 0
sample_instant_variable = SampleClass()
Copy
规则3.5 类或对象的私有成员一般用单下划线_开头;对于需要被继承的基类成员,如果想要防止与派生类成员重名,可用双下划线__开头。
说明: Python没有严格的私有权限控制,业界约定俗成的用单下划线“_”开头来暗示此成员仅供内部使用。双下划线“__”开头的成员会被解释器自动改名,加上类名作为前缀,其作用是防止在类继承场景中出现名字冲突,并不具有权限控制的作用,外部仍然可以访问。双下划线开头的成员应当只在需要避免名字冲突的场景中使用(比如设计为被继承的工具基类)。

正确示例:

class MyClass:
def my_func(self):
self._member = 1 # 单下划线开头,暗示此成员仅供类的内部操作使用,外部不应该访问。

def _my_private_func(self):   # 单下划线开头,暗示此方法仅供类的内部操作使用,外部不应该访问。
    pass

class Mapping:
def init(self, iterable):
self.items_list = []
self.__update(iterable) # 双下划线开头,会被解释器改名为_Mapping__update。外部如果使用修改后的名字仍可访问

def update(self, iterable):
    for item in iterable:
        self.items_list.append(item)

__update = update   # 作为update方法的私有复制成员,不会跟派生类成员重名

class MappingSubclass(Mapping):
# 和基类同名方法,修改了参数个数,但是不会影响基类__init__
def update(self, keys, values):
for item in zip(keys, values):
self.items_list.append(item)

__update = update   # 被解释器改名为_MappingSubclass__update,不会跟基类成员重名

Copy
参考资料:https://docs.python.org/3/tutorial/classes.html#private-variables

建议3.6 变量(variable)命名要有明确含义,使用完整的单词或大家基本可以理解的缩写,避免使人产生误解
说明:

命名中若使用了特殊约定或缩写,建议注释说明。
对于变量命名,除局部循环变量之外,不允许取单个字符(如i、j、k)。
不要用单个字符"l","o"来做变量名称。在有些字体中,这些字符于数字很难1和0很难辨认。若确实需要使用"l"做变量,用"L"来替换。
错误示例:

class SampleClass(object):
pass

def sample_function(sample_parameter):
i = SampleClass()
o = [l for l in range(1)]
Copy
正确示例:

class SampleClass(object):
pass

def sample_function(sample_parameter):
sample_inst = SampleClass()
number_list = [i for i in range(10)]
Copy
命名规范推荐表
建议3.7 命名规范推荐表
Python之父Guido推荐的命名规范

Type Public Internal
Modules lower_with_under _lower_with_under
Packages lower_with_under
Classes CapWords
Exceptions CapWords
Functions lower_with_under() _lower_with_under()
Global/Class Constants CAPS_WITH_UNDER _CAPS_WITH_UNDER
Global/Class Variables lower_with_under lower_with_under
Instance Variables lower_with_under lower_with_under (protected) or __lower_with_under (private)
Method Names lower_with_under() _lower_with_under() (protected) or __lower_with_under() (private)
Function/Method Parameters lower_with_under
Local Variables lower_with_under
4. 编码
规则4.1 与None作比较要使用“is”或“is not”,不要使用等号
说明:

“is”判断是否指向同一个对象(判断两个对象的id是否相等),“==”会调用eq方法判断是否等价(判断两个对象的值是否相等)。

示例:

同一个实例,使用“is”和“==”的判断结果不同。

class Bad(object):
def eq(self, other):
return True

bad_inst = Bad()
bad_inst == None
True

bad_inst is None
False
Copy
建议4.2 定义一个all不会把本模块的所有内容都暴露在外部,将允许外部访问的变量、函数和类的名字放进去
说明:

在模块中定义了__all__之后,从外部from module import *只会import __all__中定义的内容。

示例:

sample_package.py

all = [“sample_external_function”]

def sample_external_function():
print(“This is an external function…”)

def sample_internal_function():
print(“This is an internal function…”)
Copy
main.py

from sample_package import *

if name == “main”:
sample_external_function()
sample_internal_function()

NameError: name ‘sample_internal_function’ is not defined
Copy
建议4.3 避免直接使用dict[key]的方式从字典中获取value,如果一定要使用,需要注意当key not in dict时的异常捕获和处理
说明:

Python的字典dict可以使用key获取其对应的value。但是当key在dict的key值列表中不存在时,直接使用dict[key]获取value会报KeyError,应当使用更为安全的dict.get(key)类型方法获取value。

错误示例:

sample_dict = {‘default_key’: 1}
sample_key = ‘sample_key’
sample_value = sample_dict[sample_key]
Copy
正确示例:

sample_dict = {‘default_key’: 1}
sample_key = ‘sample_key’
sample_value = sample_dict.get(sample_key)
Copy
建议4.4 对序列使用切片操作时,不建议使用负步进值进行切片
说明:

Python提供了sample_list[start : end : stride]形式的写法,以实现步进切割,也就是从每stride个元素中取一个出来。但如果stride值为负,则会使代码难以理解,特定使用场景下还会造成错误。

错误示例:

如下写法,在start : end : stride都使用的情况下使用负的stride,会造成阅读困难。此种情况建议将“步进”切割过程和“范围”切割过程分开,使代码更清晰。

a = [1,2,3,4,5,6,7,8]
a[2::2]
[3,5,7]

a[-2::-2]
[7,5,3,1]

a[-2:2:-2]
[7,5]

a[2:2:-2]
[]
Copy
建议4.5 传递实例类型参数后,函数内应使用isinstance函数进行参数检查,不要使用type
说明:如果类型有对应的工厂函数,可使用它对类型做相应转换,否则可以使用isinstance函数来检测。使用函数/方法参数传递实例类型参数后,函数内对此参数进行检查应使用isinstance函数,使用is not None,len(para) != 0等其它逻辑方法都是不安全的。

错误示例:

下面的函数保护未能完成应有的检查功能,传入一个tuple就可以轻易绕过保护代码造成执行异常。

def sample_sort_list(sample_inst):
… if sample_inst is []:
… return
… sample_inst.sort()

fake_list = (2,3,1,4)
sample_sort_list(fake_list)
Traceback (most recent call last):
File “”, line 1, in
sample_sort_list(fake_list)
File “”, line 4, in sample_sort_list
sample_inst.sort()
AttributeError: ‘tuple’ object has no attribute ‘sort’
Copy
正确示例:

使用instance函数对入参进行检查,检查后可以按照需求raise exception或return。

def sample_sort_list(sample_inst):
… if not isinstance(sample_inst, list):
… raise TypeError(r"sample_sort_list in para type error %s" % type(sample_inst))
… sample_inst.sort()

fake_list = (2,3,1,4)
sample_sort_list(fake_list)
Traceback (most recent call last):
File “”, line 1, in
sample_sort_list(fake_list)
File “”, line 3, in sample_sort_list
raise TypeError(r"sample_sort_list in para type error %s" % type(sample_inst))
TypeError: sample_sort_list in para type error
Copy
建议4.6 尽量使用推导式代替重复的逻辑操作构造序列。但推导式必须考虑可读性,不推荐使用两个以上表达式的列表推导
说明:

推导式(comprehension)是一种精炼的序列生成写法,在可以使用推导式完成简单逻辑,生成序列的场合尽量使用推导式,但如果逻辑较为复杂(> 两个逻辑表达式),则不推荐强行使用推导式,因为这会使推导式代码的可读性变差。

错误示例:

如下逻辑代码,逻辑较为简单,实现此逻辑的代码不但有循环,而且较为复杂,性能不佳。

odd_num_list = []
for i in range(100):
if i % 2 == 1:
odd_num_list.append(i)
Copy
正确示例:

odd_num_list = [i for i in range(100) if i % 2 == 1]
Copy
简单逻辑使用列表推导式实现,代码清晰精炼。

建议4.7 功能代码应该封装在函数或类中
说明:在Python中, 所有的顶级代码在模块导入时都会被执行. 容易产生调用函数, 创建对象等误操作。所以代码应该封装在函数或类中。即使是脚本类的代码,也建议在执行主程序前总是检查 if name == ‘main’ , 这样当模块被导入时主程序就不会被执行.

正确示例:

def main():

if name == ‘main’:
main()
Copy
建议4.8 需要精确数值计算的场景,应使用decimal模块
说明:在Python中,注意不要用浮点数构造Decimal,因为浮点数本身不准确。

正确示例:

from decimal import Decimal
Decimal(‘3.14’)
Decimal(‘3.14’)

getcontext().prec = 6
Decimal(1) / Decimal(7)
Decimal(‘0.142857’)
Copy
错误示例:

from decimal import Decimal
getcontext().prec = 28
Decimal(3.14)
Decimal(‘3.140000000000000124344978758017532527446746826171875’)
Copy
规则4.9 避免对不同对象使用同一个变量名
说明: Python是弱类型语言,允许变量被赋值为不同类型对象,但这么做可能会导致运行时错误,且因为变量上下文语义变化导致代码复杂度提升,难以调试和维护,也不会有任何性能的提升。

错误示例

items = ‘a,b,c,d’ # 字符串
items = items.split(’,’) # 变更为列表
Copy
正确示例

items = ‘a,b,c,d’ # 字符串
itemList = items.split(’,’) # 变更为列表
Copy
规则4.10 类的方法不需访问实例时,根据具体场景选择使用@staticmethod或者@classmethod进行装饰
说明: 一般的类方法要接收一个self参数表示此类的实例,但有些方法不需要访问实例,这时分为两种情况: 1、方法不需要访问任何成员,或者只需要显式访问这个类自己的成员。这样的方法不需要额外参数,应当用@staticmethod装饰。 在Python 3.X版本中,允许直接定义不含self参数的方法,并且允许不通过实例调用。但是一旦通过实例调用这个方法,就会因为参数不匹配而出错。 加上@staticmethod进行修饰,可以让Python解释器明确此方法不需要self参数,提前拦截问题,可读性也更好。

错误示例:

class MyClass:
def my_func(): # 没有用@staticmethod修饰,通过实例调用会出错
pass

MyClass.my_func() # Python 3.X中允许,2.X中出错
my_instance = MyClass()
my_instance.my_func() # Python 3.X和2.X中都会出错
Copy
正确示例:

class MyClass:
@staticmethod
def my_func(): # 用@staticmethod修饰后,解释器会将其解析为静态方法
pass

MyClass.my_func() # OK
my_instance = MyClass()
my_instance.my_func() # OK,但是不推荐,容易和普通方法混淆。最好写成MyClass.my_func()
Copy
2、方法不需要访问实例的成员,但需要访问基类或派生类的成员。这时应当用@classmethod装饰。装饰后的方法,其第一个参数不再传入实例,而是传入调用者的最底层类。 比如,下面这个例子,通过基类Spam的count方法,来统计继承树上每个类的实例个数:

class Spam:
numInstances = 0
@classmethod
def count(cls): # 对每个类做独立计数
cls.numInstances += 1 # cls是实例所属于的最底层类
def init(self):
self.count() # 将self.__class__传给count方法

class Sub(Spam):
numInstances = 0

class Other(Spam):
numInstances = 0

x = Spam()
y1, y2 = Sub(), Sub()
z1, z2, z3 = Other(), Other(), Other()
x.numInstances, y1.numInstances, z1.numInstances # 输出:(1, 2, 3)
Spam.numInstances, Sub.numInstances, Other.numInstances # 输出:(1, 2, 3)
Copy
但是使用@classmethod时需要注意,由于在继承场景下传入的第一个参数并不一定是这个类本身,因此并非所有访问类成员的场景都应该用@classmethod。比如下面这个例子中,Base显式的想要修改自己的成员inited(而不是派生类的成员),这时应当用@staticmethod。

错误示例:

class Base:
inited = False
@classmethod
def set_inited(cls): # 实际可能传入Derived类
cls.inited = True # 并没有修改Base.inited,而是给Derived添加了成员

class Derived(Base):
pass

x = Derived()
x.set_inited()
if Base.inited:
print(“Base is inited”) # 不会被执行
Copy
建议4.11 当多个Python源码文件分不同子目录存放时,用包(package)形式管理各个目录下的模块。
说明: 通过让子目录包含__init__.py文件,可以让Python代码在import和from语句中,将子目录作为包名,通过分层来管理各个模块,让模块间的关系更清楚。init.py文件中可以包含这个包所需要的初始化动作,也可以定义一个__all__列表来指定from *语句会包含哪些模块。对于不需要初始化的包,可以只在目录下放一个名为__init__.py的空文件,标识这个目录是一个包。

正确示例: 假设Python源码根目录是dir0,其下有子目录dir1,dir1下面又有个子目录dir2,dir2下面有个mod.py模块。 那么,在dir1和dir2下各放置一个__init__.py文件,然后在其他代码中可以这样使用mod.py模块:

import dir1.dir2.mod
dir1.dir2.mod.func() # 调用mod.py中的func函数

from dir1.dir2.mod import func # 把func函数添加到当前空间
func() # 可以省掉包名和模块名直接调用
Copy
建议4.12 避免在代码中修改sys.path列表
说明: sys.path是Python解释器在执行import和from语句时参考的模块搜索路径,由当前目录、系统环境变量、库目录、.pth文件配置组合拼装而成。用户通过修改系统配置,可以指定搜索哪个路径下的模块。sys.path只应该根据用户的系统配置来生成,不应该在代码里面直接修改。否则可能出现A模块修改了sys.path,导致B模块搜索出错,且用户难以定位。

正确示例: 如果要添加模块搜索路径,应当修改PYTHONPATH环境变量。如果是管理子目录,应当通过包(package)来组织模块。

建议4.13 尽量不使用for i in range(x)的方式循环处理集合数据,而应使用for x in iterable的方式
说明: for i in range(x),然后在循环体内对集合用下标[i]获取元素是C语言的编程习惯,它有很多缺点:容易越界;在循环体内修改i容易出错;可读性差。Python语言建议尽量用for x in iterable的方式直接取集合的每一条数据进行处理。

错误示例:

for i in range(len(my_list)):
print(my_list[i])
Copy
正确示例:

for x in my_list:
print(x)
Copy
有些场合下,需要在处理时使用每个元素的序号,这时可以使用enumerate内置函数来给元素加上序号形成元组:

my_list = [‘a’, ‘b’, ‘c’]
for x in enumerate(my_list):
print(x)
Copy
运行结果为: (0, ‘a’) (1, ‘b’) (2, ‘c’)

规则4.14 避免在无关的变量或无关的概念之间重用名字,避免因重名而导致的意外赋值和错误引用
说明: Python的函数/类定义和C语言不同,函数/类定义语句实际上是给一个名字赋值。因此重复定义一个函数/类的名字不会导致错误,后定义的会覆盖前面的。但是重复定义很容易掩盖编码问题,让同一个名字的函数/类在不同的执行阶段具有不同的含义,不利于可读性,应予以禁止。 Python在解析一个被引用的名字时遵循LEGB顺序(Local - Enclosed - Global - Builtin),从内层一直查找到外层。内层定义的变量会覆盖外层的同名变量。在代码修改时,同名的变量容易导致错误的引用,也不利于代码可读性,应当尽量避免。

  1. 异常处理
    异常处理
    规则5.1 使用try…except…结构对代码作保护时,需要在异常后使用finally…结构保证操作对象的释放
    说明:

使用try…except…结构对代码作保护时,如果代码执行出现了异常,为了能够可靠地关闭操作对象,需要使用finally…结构确保释放操作对象。

示例:

handle = open(r"/tmp/sample_data.txt") # May raise IOError
try:
data = handle.read() # May raise UnicodeDecodeError
except UnicodeDecodeError as decode_error:
print(decode_error)
finally:
handle.close() # Always run after try:
Copy
规则5.2 不要使用“except:”语句来捕获所有异常
说明:

在异常这方面, Python非常宽容,“except:”语句真的会捕获包括Python语法错误在内的任何错误。使用“except:”很容易隐藏真正的bug,我们在使用try…except…结构对代码作保护时,应该明确期望处理的异常。 Exception类是大多数运行时异常的基类,一般也应当避免在except语句中使用。通常,try只应当包含必须要在当前位置处理异常的语句,except只捕获必须处理的异常。比如对于打开文件的代码,try应当只包含open语句,except只捕获FileNotFoundError异常。对于其他预料外的异常,则让上层函数捕获,或者透传到程序外部来充分暴露问题。

错误示例:

如下代码可能抛出两种异常,使用“except:”语句进行统一处理时,如果是open执行异常,将在“except:”语句之后handle无效的情况下调用close,报错handle未定义。

try:
handle = open(r"/tmp/sample_data.txt") # May raise IOError
data = handle.read() # May raise UnicodeDecodeError
except:
handle.close()
Copy
正确示例:

try:
handle = open(r"/tmp/sample_data.txt") # May raise IOError
try:
data = handle.read() # May raise UnicodeDecodeError
except UnicodeDecodeError as decode_error:
print(decode_error)
finally:
handle.close()

except(FileNotFoundError, IOError) as file_open_except:
print(file_open_except)
Copy
规则5.3 不在except分支里面的raise都必须带异常
说明:raise关键字单独使用只能出现在try-except语句中,重新抛出except抓住的异常。

错误示例:

a = 1
if a==1:
… raise

Traceback (most recent call last):
File “”, line 2, in
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType
Copy
正确示例1:raise一个Exception或自定义的Exception

a = 1
if a==1:
… raise Exception

Traceback (most recent call last):
File “”, line 2, in
Exception
Copy
正确示例2:在try-except语句中使用

import sys

try:
… f = open(‘myfile.txt’)
… s = f.readline()
… i = int(s.strip())
… except IOError as e:
… print “I/O error({0}): {1}”.format(e.errno, e.strerror)
… except ValueError:
… print “Could not convert data to an integer.”
… except:
… print “Unexpected error:”, sys.exc_info()[0]
… raise
Copy
建议5.4 尽量用异常来表示特殊情况,而不要返回None
当我们在一个工具方法时,通常会返回None来表明特殊的意义,比如一个数除以另外一个数,如果被除数为零,那么就返回None来表明是没有结果的

def divide(a, b):
try:
return a/b
except ZeroDivisionError:
return None

result = divide(x, y)
if result is None:
print(‘Invalid inputs’)
Copy
当分子为零是会返回什么?应该是零(如果分母不为零的话),上面的代码在if条件检查就会被忽略掉,if条件不仅仅只检查值为None,还要添加所有条件为False的情况了

x, y = 0, 5
result = divide(x, y)
if not result:
print(‘Invalid inputs’) #This is wrong!
Copy
上面的情况是python编码过程中很常见,这也为什么方法的返回值为None是一种不可取的方式,这里有两种方法来避免上面的错误。

1.第一种方法是将返回值分割成一个tuple,第一部分表示操作是否成功,第二部分是实际的返回值(有点象go语言里的处理)

def divide(a, b):
try:
return True, a / b
except ZeroDivisionError:
return False, None
Copy
调用此方法时获取返回值并解开,检查第一部分来代替之前仅仅检查结果。

success, result = divide(x, y)
if not success:
print(‘Invalid inputs’)
Copy
这种方式会带来另外一个问题,方法的调用者很容易忽略掉tuple的第一部分(通过在python里可以使用_来标识不使用的变量),这样的代码乍一看起来不错,但是实际上和直接返回None没什么两样。

_, result = divide(x, y)
if not result:
print(‘Invalid inputs’)
Copy
2.接下来,另外一种方式,也是推荐的一种方式,就是触发异常来让调用者来处理,方法将触发ValueError来包装现有的ZeroDivisionError错误用来告诉方法调用者输入的参数是有误的。

def divide(a, b):
try:
return a / b
except ZeroDivisionError as e:
raise ValueError(‘Invalid inputs’) from e
Copy
那么方法调用者必须要处理错误输入值而产生的异常(方法的注释应该注明异常情况)。同时也不用去检查返回值,因为当方法没有异常抛出时,返回值一定是对的,对于异常的处理也是很清晰。

x, y = 5, 2
try:
result = divide(x, y)
except ValueError:
print(‘Invalid inputs’)
else:
print(‘Result is %.1f’ % result)

Result is 2.5
Copy
需要记住的: (1)方法使用None作为特殊含义做为返回值是非常糟糕的编码方式,因为None和其它的返回值必须要添加额外的检查代码。 (2)触发异常来标示特殊情况,调用者会在捕获异常来处理。

建议5.5 避免finally中可能发生的陷阱,不要在finally中使用return或者break语句
通常使用finally语句,表明要释放一些资源,这时候try和except还有else代码块都被执行过了,如果在执行它们的过程中有异常触发,且没有处理这个异常,那么异常会被暂存,当finally代码执行后,异常会重新触发,但是当finally代码块里有return或break语句时,这个暂存的异常就会丢弃:

def f():
try:
1/0
finally:
return 42

print(f())
Copy
上面的代码执行完后1/0产生的异常就会被忽略,最终输出42,因此在finally里出现return是不可取的。

当try块中return,break,continue执行时,finally块依然会被执行。

def foo():
try:
return ‘try’
finally:
return ‘finally’

foo()
‘finally’
Copy
最终方法的输出其实不是正确的结果,但出现这个问题的原因是错误使用了return和break语句。

规则5.6 禁止使用except X, x语法,应当使用except X as x
说明: except X, x语法只在2.X版本支持,3.X版本不支持,有兼容性问题。而且,except X, x写法容易和多异常捕获的元组(tuple)表达式混淆。因此应该统一用except X as x方式。

异常恢复
建议5.7 方法发生异常时要恢复到之前的对象状态
说明:当发生异常的时候,对象一般需要——如果是关键的安全对象则必须——维持其状态的一致性。常用的可用来维持对象状态一致性的手段包括:

输入校验(如校验方法的调用参数)
调整逻辑顺序,使可能发生异常的代码在对象被修改之前执行
当业务操作失败时,进行回滚
对一个临时的副本对象进行所需的操作,直到成功完成这些操作后,才把更新提交到原始的对象
避免需要去改变对象状态
错误示例:

PADDING = 2
MAX_DIMENSION = 10

class Dimensions:
def init(self, length, width, height):
self.length = length
self.width = width
self.height = height

def get_volume_package(self, weight):
    self.length += PADDING
    self.width += PADDING
    self.height += PADDING
    try:
        self.validate(weight)
        volume = self.length * self.width * self.height
        self.length -= PADDING
        self.width -= PADDING
        self.height -= PADDING
        return volume
    except Exception , ex:
        return -1

def validate(self, weight) :
    # do some validation and may throw a exception
    if weight>20:
        raise Exception
    pass

if name == ‘main
d = Dimensions(10, 10, 10)
print d.getVolumePackage(21) # Prints -1 (error)
print d.getVolumePackage(19) # Prints 2744 instead of 1728
Copy
在这个错误示例中,未有异常发生时,代码逻辑会恢复对象的原始状态。但是如果出现异常事件,则回滚代码不会被执行,从而导致后续的getVolumePackage()调用不会返回正确的结果。

正确示例1:回滚

except Exception , ex:
self.length -= PADDING
self.width -= PADDING
self.height -= PADDING
return -1
Copy
这个正确示例在getVolumePackage()方法的except块中加入了发生异常时恢复对象状态的代码。

正确示例2:finally子句

def getVolumePackage(self, weight):
self.length += PADDING
self.width += PADDING
self.height += PADDING
try:
self.validate(weight)
volume = self.length * self.width * self.height
return volume
except Exception , ex:
return -1
finally:
self.length -= PADDING
self.width -= PADDING
self.height -= PADDING
Copy
这个正确示例使用一个finally子句来执行回滚操作,以保证不管是否发生异常,都会进行回滚。

正确示例3:输入校验

def getVolumePackage(self, weight):
try:
self.validate(weight)
except Exception , ex:
return -1

self.length += PADDING
self.width += PADDING
self.height += PADDING
volume = self.length * self.width * self.height
self.length -= PADDING
self.width -= PADDING
self.height -= PADDING
return volume

Copy
这个正确示例在修改对象状态之前执行输入校验。注意,try代码块中只包含可能会抛出异常的代码,而其他代码都被移到try块之外。

正确示例4:未修改的对象

def getVolumePackage(self, weight):
try:
self.validate(weight)
except Exception , ex:
return -1

self.length += PADDING
self.width += PADDING
self.height += PADDING
volume = (self.length + PADDING) * (self.width + PADDING) * (self.height + PADDING)
return volume

Copy
这个正确示例避免了需要修改对象,使得对象状态不可能不一致,也因此没有必要进行回滚操作。相比之前的解决方案,更推荐使用这种方式。但是对于一些复杂的代码,这种方式可能无法实行。

断言
建议5.8 assert语句通常只在测试代码中使用,禁止在生产版本中包含assert功能
assert语句用来声明某个条件是真的。例如,如果你非常确信某个列表中至少有一个元素,而你想检验这一点,并且在它非真时触发一个异常,那么assert就是这种场景下的不二之选。当assert语句失败的时候,会触发AssertionError异常

mylist = [‘item’]
assert len(mylist) >= 1
mylist.pop()
‘item’

assert len(mylist) >= 1
Traceback (most recent call last): File “”, line 1, in ? AssertionError
Copy
assert只应在研发过程中内部测试时使用,出现了AssertionError异常说明存在软件设计或者编码上的错误,应当修改软件予以解决。在对外发布的生产版本中禁止包含assert功能。

你可能感兴趣的:(编码规范)