黑马程序员技术交流社区

标题: 【上海校区】Python 最差实践 [打印本页]

作者: 不二晨    时间: 2018-8-10 11:29
标题: 【上海校区】Python 最差实践

最近在看一些陈年老系统,其中有一些不好的代码习惯遗留下来的坑;加上最近自己也写了一段烂代码导致服务器负载飙升,所以就趁此机会总结下我看到过/写过的自认为不好的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]

理解这其中的原因,最重要的是有两点:

正确的做法如下:

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 dictif 'list' not in d:        d['list'] = []d['list'].append(x)

完全可以用这样一行代码替代:

# d is a dictd.setdefault('list', []).append(x)

同样的,下面这两种写法一看就是带有浓浓的C味儿:

# d is a dictfor k in d:        v = d[k]        # do something# l is a listfor i in len(l):        v = l        # do something

应该用更pythonic的写法:

# d is a dictfor k, v in d.iteritems():        # do something        pass# l is a listfor i, v in enumerate(l):        # do something        pass

另外,enumerate其实还有个第二参数,表示序号从几开始。如果想要序号从1开始数起,可以使用enumerate(l, 1)。

使用flag变量而不使用for…else语句

同样,这样的代码也很常见:

search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey']found = Falsefor s in search_list:    if s.startswith('C'):        found = True        # do something when found        print('Found')        breakif 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')        breakelse:    # do something when not found    print('Not found')过度使用tuple unpacking

在Python中,允许对tuple类型进行unpack操作,如下所示:

# human = ('James', 180, 32)name,height,age = human

这个特性用起来很爽,比写name=human[0]之类的不知道高到哪里去了。所以,这一特性往往被滥用,一个human在程序的各处通过上面的方式unpack。

然而如果后来需要在human中插入一个表示性别的数据sex,那么对于所有的这种unpack都需要进行修改,即使在有些逻辑中并不会使用到性别。

# human = ('James', 180, 32)name,height,age, _ = human# or# name, height, age, sex = 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 *中找到一个第三方函数的来源。

可以说这种习惯是百害而无一利的。

文件操作

文件操作不要使用裸奔的f = open(‘filename’)了,使用with open(‘filename’) as f来让context manager帮你处理异常情况下的关闭文件等乱七八糟的事情多好。

野蛮使用class.name判断类型

我曾经遇见过一个bug:为了实现某特定功能,我新写了一个class B(A),在B中重写了A的若干函数。整个实现很简单,但是就是有一部分A的功能无法生效。最后追查到的原因,就是在一些逻辑代码中,硬性的判断了entity.__class__.__name__ == ‘A’。

除非你就是想限定死继承层级中的当前类型(也就是,屏蔽未来可能会出现的子类),否则,不要使用__class__.__name__,而改用isinstance这个内建函数。毕竟,Python把这两个变量的名字都刻意带上那么多下划线,本来就是不太想让你用嘛。

循环内部有多层函数调用

循环内部有多层函数调用,有如下两方面的隐患:

所以我建议,在循环内部,如非特别复杂的逻辑,都应该直接写在循环里,不要进行函数调用。如果一定要包装一层函数调用,应该在函数的命名或注释中,提示后续的维护者,这个函数会在循环内部使用。

Python是一门非常容易入门的语言,严格的缩进要求和丰富的内置数据类型,使得大部分Python代码都能做到比较好的规范。但是,不严格要求自己,也很容易就写出犯二的代码。上面列出的只是很小的一部分,唯有多读、多写、多想,才能培养敏锐的代码嗅觉,第一时间发现坏味道啊。


【转载】http://www.codeceo.com/article/python-bad-practice.html



作者: 梦缠绕的时候    时间: 2018-8-10 15:16

作者: 小影姐姐    时间: 2018-8-13 10:33

作者: 不二晨    时间: 2018-8-16 17:01
奈斯




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2