python通用规范-4

文章目录

4.1 `None`值比较
4.2 模块导入控制 __all__
4.3 字典取值的推荐方式(`get`)
4.4 列表切边不推荐负步长值
4.5 参数的类型检查推荐`isinstance`
4.6 使用列表推导式替换循环
4.7 功能代码应该封装在函数或类中
4.8 精确数值计算的场景使用`Decimal`模块
4.9 避免对不同对象使用同一个变量名
4.10 类方法的装饰
4.11 使用包(`package`)形式管理不同目录下的源码
4.12 避免在代码中修改`sys.path`列表
4.13 使用枚举替代`range`
4.14 避免重复定义变量名
4.1 None值比较

与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
1
2
3
4
5
6
7
8
4.2 模块导入控制 all

定义一个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..")


# main.py
from sample_package import *

if __name__ == "__main__":
sample_external_function()
sample_internal_function().

NameError: name 'sample_internal_function' is not defined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4.3 字典取值的推荐方式(get)

避免直接使用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]

# 正确示例:
sample_dict = {'default_key': 1}
sample_key = 'sample_key'
sample_value = sample_dict.get(sample_key)
1
2
3
4
5
6
7
8
9
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]
[]
1
2
3
4
5
6
7
8
9
10
11
4.5 参数的类型检查推荐isinstance

传递实例类型参数后,函数内应使用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 "<pyshell#232>", line 1, in <module>
sample_sort_list(fake_list)
File "<pyshell#230>", line 4, in sample_sort_list
sample_inst.sort()
AttributeError: 'tuple' object has no attribute 'sort'

# 正确示例:
# 使用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 "<pyshell#235>", line 1, in <module>
sample_sort_list(fake_list)
File "<pyshell#234>", 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 <type 'tuple'>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
4.6 使用列表推导式替换循环

尽量使用推导式代替重复的逻辑操作构造序列。但推导式必须考虑可读性,不推荐使用两个以上表达式的列表推导

说明:
推导式(comprehension)是一种精炼的序列生成写法,在可以使用推导式完成简单逻辑,生成序列的场合尽量使用推导式,但如果逻辑较为复杂(> 两个逻辑表达式),则不推荐强行使用推导式,因为这会使推导式代码的可读性变差。
# 错误示例:
# 如下逻辑代码,逻辑较为简单,实现此逻辑的代码不但有循环,而且较为复杂,性能不佳。
odd_num_list = []
for i in range(100):
if i % 2 == 1:
odd_num_list.append(i)
# 正确示例:
odd_num_list = [i for i in range(100) if i % 2 == 1]
# 简单逻辑使用列表推导式实现,代码清晰精炼。
1
2
3
4
5
6
7
8
9
4.7 功能代码应该封装在函数或类中

说明:在Python中, 所有的顶级代码在模块导入时都会被执行. 容易产生调用函数, 创建对象等误操作。所以代码应该封装在函数或类中。即使是脚本类的代码,也建议在执行主程序前总是检查 if __name__ == '__main__' , 这样当模块被导入时主程序就不会被执行.
正确示例:
def main():
...
if __name__ == '__main__':
main()
1
2
3
4
5
4.8 精确数值计算的场景使用Decimal模块

说明:在Python中,注意不要用浮点数构造Decimal,因为浮点数本身不准确。
# 错误示例:
>>> from decimal import Decimal
>>> getcontext().prec = 28
>>> Decimal(3.14)
Decimal('3.140000000000000124344978758017532527446746826171875')

# 正确示例:
>>> from decimal import Decimal
>>> Decimal('3.14')
Decimal('3.14')
>>> getcontext().prec = 6
>>> Decimal(1) / Decimal(7)
Decimal('0.142857')
1
2
3
4
5
6
7
8
9
10
11
12
13
4.9 避免对不同对象使用同一个变量名

说明: Python是弱类型语言,允许变量被赋值为不同类型对象,但这么做可能会导致运行时错误,且因为变量上下文语义变化导致代码复杂度提升,难以调试和维护,也不会有任何性能的提升。
# 错误示例
items = 'a,b,c,d' # 字符串
items = items.split(',') # 变更为列表

# 正确示例
items = 'a,b,c,d' # 字符串
itemList = items.split(',') # 变更为列表
1
2
3
4
5
6
7
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中都会出错


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


MyClass.my_func() # OK
my_instance = MyClass()
my_instance.my_func() # OK,但是不推荐,容易和普通方法混淆。最好写成MyClass.my_func()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
但是使用@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") # 不会被执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4.11 使用包(package)形式管理不同目录下的源码

当多个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() # 可以省掉包名和模块名直接调用
1
2
3
4
4.12 避免在代码中修改sys.path列表

说明: sys.path是Python解释器在执行import和from语句时参考的模块搜索路径,由当前目录、系统环境变量、库目录、.pth文件配置组合拼装而成。用户通过修改系统配置,可以指定搜索哪个路径下的模块。sys.path只应该根据用户的系统配置来生成,不应该在代码里面直接修改。否则可能出现A模块修改了sys.path,导致B模块搜索出错,且用户难以定位。
正确示例: 如果要添加模块搜索路径,应当修改PYTHONPATH环境变量。如果是管理子目录,应当通过包(package)来组织模块。

4.13 使用枚举替代range

尽量不使用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])

# 正确示例:
for x in my_list:
print(x)
# 有些场合下,需要在处理时使用每个元素的序号,这时可以使用enumerate内置函数来给元素加上序号形成元组:
my_list = ['a', 'b', 'c']
for x in enumerate(my_list):
print(x)
# 运行结果为: (0, 'a') (1, 'b') (2, 'c')
1
2
3
4
5
6
7
8
9
10
11
12
4.14 避免重复定义变量名

避免在无关的变量或无关的概念之间重用名字,避免因重名而导致的意外赋值和错误引用

说明: Python的函数/类定义和C语言不同,函数/类定义语句实际上是给一个名字赋值。因此重复定义一个函数/类的名字不会导致错误,后定义的会覆盖前面的。但是重复定义很容易掩盖编码问题,让同一个名字的函数/类在不同的执行阶段具有不同的含义,不利于可读性,应予以禁止。 Python在解析一个被引用的名字时遵循LEGB顺序(Local - Enclosed - Global - Builtin),从内层一直查找到外层。内层定义的变量会覆盖外层的同名变量。在代码修改时,同名的变量容易导致错误的引用,也不利于代码可读性,应当尽量避免。
————————————————
版权声明:本文为CSDN博主「zhao12501」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhao12501/article/details/115455806

posted @ 2021-07-11 00:13  physique  阅读(398)  评论(0编辑  收藏  举报