最近在看一些陈年老系统,其中有一些不好的代码习惯遗留下来的坑;加上最近自己也写了一段烂代码导致服务器负载飙升,所以就趁此机会总结下我看到过/写过的自认为不好的Python代码习惯,时刻提醒自己远离这些“最差实践”,避免挖坑。
下面所举的例子中,有一部分会造成性能问题,有一部分会导致隐藏bug,或日后维护、重构困难,还有一部分纯粹是我认为不够pythonic。所以大家自行甄别,取精去糟吧。
函数默认参数使用可变对象
这个例子我想大家应该在各种技术文章中见过许多遍了,也足以证明这是一个大坑。
先看错误示范吧:
def use_mutable_default_param(idx=0, ids=[]): ids.append(idx) print(idx) print(ids) use_mutable_default_param(idx=1) use_mutable_default_param(idx=2)
输出:
1 [1] 2 [1, 2]
理解这其中的原因,最重要的是有两点:
函数本身也是一个对象,默认参数绑定于这个函数对象上
append这类方法会直接修改对象,所以下次调用此函数时,其绑定的默认参数已经不再是空list了
正确的做法如下:
def donot_use_mutable_default_param(idx=0, ids=None): if ids is None: ids = [] ids.append(idx) print(idx) print(ids)
try…except不具体指明异常类型
虽然在Python中使用try…except不会带来严重的性能问题,但是不加区分,直接捕获所有类型异常的做法,往往会掩盖掉其他的bug,造成难以追查的bug。
一般的,我觉得应该尽量少的使用try…except,这样可以在开发期尽早的发现问题。即使要使用try…except,也应该尽可能的指定出要捕获的具体异常,并在except语句中将异常信息记入log,或者处理完之后,再直接raise出来。
关于dict的冗余代码
我经常能够看到这样的代码:
d = {} datas = [1, 2, 3, 4, 2, 3, 4, 1, 5] for k in datas: if k not in d: d[k] = 0 d[k] += 1
其实,完全可以使用collections.defaultdict这一数据结构更简单优雅的实现这样的功能:
default_d = defaultdict(lambda: 0) datas = [1, 2, 3, 4, 2, 3, 4, 1, 5] for k in datas: default_d[k] += 1
同样的,这样的代码:
# d is a dict if 'list' not in d: d['list'] = [] d['list'].append(x)
完全可以用这样一行代码替代:
# d is a dict d.setdefault('list', []).append(x)
同样的,下面这两种写法一看就是带有浓浓的C味儿:
# d is a dict for k in d: v = d[k] # do something # l is a list for i in len(l): v = l[i] # do something
应该用更pythonic的写法:
# d is a dict for k, v in d.iteritems(): # do something pass # l is a list for i, v in enumerate(l): # do something pass
另外,enumerate其实还有个第二参数,表示序号从几开始。如果想要序号从1开始数起,可以使用enumerate(l, 1)。
使用flag变量而不使用for…else语句
同样,这样的代码也很常见:
search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey'] found = False for s in search_list: if s.startswith('C'): found = True # do something when found print('Found') break if not found: # do something when not found print('Not found')
其实,用for…else更优雅:
search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey'] for s in search_list: if s.startswith('C'): # do something when found print('Found') break else: # do something when not found print('Not found')
过度使用tuple unpacking
在Python中,允许对tuple类型进行unpack操作,如下所示:
1 2 |
|
这个特性用起来很爽,比写name=human[0]之类的不知道高到哪里去了。所以,这一特性往往被滥用,一个human在程序的各处通过上面的方式unpack。
然而如果后来需要在human中插入一个表示性别的数据sex,那么对于所有的这种unpack都需要进行修改,即使在有些逻辑中并不会使用到性别。
# human = ('James', 180, 32) name,height,age, _ = human # or # name, height, age, sex = human
有如下几种方式解决这一问题:
老老实实写name=human[0]这种代码,在需要使用性别信息处加上sex=human[3]
使用dict来表示human
# human = namedtuple('human', ['name', 'height', 'age', 'sex']) h = human('James', 180, 32, 0) # then you can use h.name, h.sex and so on everywhere.
到处都是import *
import *是一种懒惰的行为,它不仅会污染当前的命名空间,并且还会使得pyflakes等代码检查工具失效。在后续查看代码或者debug的过程中,往往也很难从一堆import *中找到一个第三方函数的来源。
可以说这种习惯是百害而无一利的。