python通用规范-8

文章目录

8.1 可变参数默认值设为`None`
8.2 对子类继承的变量要做显式定义和赋初值
8.3 严禁使用注释行等形式仅使功能失效
8.4 慎用`copy`和 `deepcopy`
8.5 系统路径推荐使用 `pathlib.Path`
8.6 使用`subprocess`模块代替`os.system`模块来执行`shell`命令
8.7 建议使用with语句操作文件
8.1 可变参数默认值设为None

函数参数中的可变参数不要使用默认值,在定义时使用None
说明:参数的默认值会在方法定义被执行时就已经设定了,这就意味着默认值只会被设定一次,当函数定义后,每次被调用时都会有"预计算"的过程。当参数的默认值是一个可变的对象时,就显得尤为重要,例如参数值是一个list或dict,如果方法体修改这个值(例如往list里追加数据),那么这个修改就会影响到下一次调用这个方法,这显然不是一种好的方式。应对种情况的方式是将参数的默认值设定为None。
错误示例:

>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified
... bar.append("baz") # but this line could be problematic, as we'll see...
... return bar
1
2
3
在上面这段代码里,一旦重复调用foo()函数(没有指定一个bar参数),那么将一直返回’bar’。因为没有指定参数,那么foo()每次被调用的时候,都会赋予[]。下面来看看,这样做的结果:

>>> foo()
["baz"]
>>> foo()
["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]

# 正确示例:None是不错的选择
>>> def foo(bar=None):
... if bar is None: # or if not bar:
... bar = []
... bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
8.2 对子类继承的变量要做显式定义和赋初值

说明:在Python中,类变量都是作为字典进行内部处理的,并且遵循方法解析顺序(MRO)。子类没有定义的属性会引用基类的属性值,如果基类的属性值发生变化,对应的子类引用的基类的属性的值也相应发生了变化。
# 错误示例:
class A(object):
x = 1
class B(A):
pass
class C(A):
pass
>>> B.x = 2
>>> print(A.x, B.x, C.x)
1 2 1
>>> A.x = 3
>>> print(A.x, B.x, C.x)
3 2 3
1
2
3
4
5
6
7
8
9
10
11
12
13
这里虽然没有给C.x赋值,但是由于基类的值A.x发生改变,在获取C.x的值得时候发现它引用的数据发生了变化。在上面这段代码中,因为属性x没有在类C中发现,它会查找它的基类(在上面例子中只有A,尽管Python支持多继承)。换句话说,就是C自己没有x属性,因此,引用C.x其实就是引用A.x。

# 正确示例:
# 如果希望类`C`中的`x`不引用自`A`类,可以在`C`类中重新定义属性`X`,
# 这样`C`类的就不会引用`A`类的属性`x`了,值的变化就不会相互影响。
class B(A):
x = 2
class C(A):
x = -1
>>> print(A.x, B.x, C.x)
1 2 -1
>>> A.x = 3
>>> print(A.x, B.x, C.x)
3 2 -1
1
2
3
4
5
6
7
8
9
10
11
12
8.3 严禁使用注释行等形式仅使功能失效

说明:python的注释包含:单行注释、多行注释、代码间注释、doc string等。除了doc string是使用""""""括起来的多行注释,常用来描述类或者函数的用法、功能、参数、返回等信息外,其余形式注释都是使用#符号开头用来注释掉#后面的内容。基于python语言运行时编译的特殊性,如果在提供代码的时候提供的是py文件,即便是某些函数和方法在代码中进行了注释,别有用心的人依然可以通过修改注释来使某些功能启用;尤其是某些接口函数,如果不在代码中进行彻底删除,很可能在不知情的情况下就被启用了某些本应被屏蔽的功能。因此根据红线要求,在python中不使用的功能、模块、函数、变量等一定要在代码中彻底删除,不给安全留下隐患。即便是不提供源码py文件,提供编译过的pyc、pyo文件,别有用心的人可以通过反编译来获取源代码,可能会造成不可预测的结果。
# 错误示例:在 main.py 中有两个接口被注释掉了,但是没有被删除。
if __name__ == "__main__":
if sys.argv[1].startswith('--'):
option = sys.argv[1][2:]
if option == "load":
#安装应用
LoadCmd(option, sys.argv[2:3][0])
elif option == 'unload':
#卸载应用
UnloadCmd(sys.argv[2:3][0])
elif option == 'unloadproc':
#卸载流程
UnloadProcessCmd(sys.argv[2:3][0])
# elif option == 'active':
# ActiveCmd(sys.argv[2:3][0])
# elif option == 'inactive':
# InActiveCmd(sys.argv[2:3][0])
else:
Loginfo("Command %s is unknown"%(sys.argv[1]))
# 在上例中很容易让其他人看到我们程序中的两个屏蔽的接口,容易造成不安全的因素,注释的代码应该删除。

# 正确实例
if __name__ == "__main__":
if sys.argv[1].startswith('--'):
option = sys.argv[1][2:]
if option == "load":
#安装应用
LoadCmd(option, sys.argv[2:3][0])
elif option == 'unload':
#卸载应用
UnloadCmd(sys.argv[2:3][0])
elif option == 'unloadproc':
#卸载流程
UnloadProcessCmd(sys.argv[2:3][0])
else:
Loginfo("Command %s is unknown"%(sys.argv[1]))
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
30
31
32
33
34
35
36
8.4 慎用copy和 deepcopy

说明:在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。如果需要拷贝对象,需要使用标准库中的copy模块。copy模块提供copy和deepcopy两个方法:
copy浅拷贝:拷贝一个对象,但是对象的属性还是引用原来的。对于可变类型,比如列表和字典,只是复制其引用。基于引用所作的改变会影响到被引用对象。
deepcopy深拷贝:创建一个新的容器对象,包含原有对象元素(引用)全新拷贝的引用。外围和内部元素都拷贝对象本身,而不是引用。
Notes:对于数字,字符串和其他原子类型对象等,没有被拷贝的说法。如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。使用copy和deepcopy时,需要了解其使用场景,避免错误使用。

# 示例:
>>> import copy
>>> a = [1, 2, ['x', 'y']]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> a.append(3)
>>> a[2].append('z')
>>> a.append(['x', 'y'])
>>> print(a)
[1, 2, ['x', 'y', 'z'], 3, ['x', 'y']]
>>> print(b)
[1, 2, ['x', 'y', 'z'], 3, ['x', 'y']]
>>> print(c)
[1, 2, ['x', 'y', 'z']]
>>> print(d)
[1, 2, ['x', 'y']]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
8.5 系统路径推荐使用 pathlib.Path

使用pathlib.Path库中的方法代替字符串拼接来完成文件系统路径的操作

说明:pathlib.Path 基于 os.path库实现了一系列文件系统路径操作方法,这些方法相比单纯的路径字符串拼接来说更为安全,而且为用户屏蔽了不同操作系统之间的差异。
# 错误示例:如下路径字符串的拼接在Windows操作系统无法使用
path = os.getcwd() + '/myDirectory'

# 正确示例:
path = pathlib.Path(os.getcwd(), 'myDirectory')
path = pathlib.Path().resolve() / 'myDirectory'
1
2
3
4
5
6
8.6 使用subprocess模块代替os.system模块来执行shell命令

说明:subprocess模块可以生成新进程,连接到它们的input/output/error管道,并获取它们的返回代码。该模块旨在替换os.system等旧模块,相比os.system模块来说更为灵活。
# 推荐做法:
>>> subprocess.run(["ls", "-l"]) # doesn't capture output
CompletedProcess(args=['ls', '-l'], returncode=0)
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
>>> subprocess.run(["ls", "-l", "/dev/null"], capture_output=True)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')
1
2
3
4
5
6
7
8
9
10
8.7 建议使用with语句操作文件

说明:Python 对一些内建对象进行改进,加入了对上下文管理器的支持,可以用于 with 语句中。使用 with 语句可以自动关闭文件,减少文件读取操作错误的可能性,在代码量和健壮性上更优。注意 with 语句要求其操作的类型实现 __enter__() 和 __exit__() 方法,需确认实现后再使用。
# 推荐做法:
with open(r'somefileName') as somefile:
for line in somefile:
print(line)
# ...more code
# 此使用with语句的代码等同于以下使用try...finally...结构的代码。

somefile = open(r'somefileName')
try:
for line in somefile:
print(line)
# ...more code
finally:
somefile.close()
————————————————
版权声明:本文为CSDN博主「zhao12501」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhao12501/article/details/115473209

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