盘点提高 Python 代码效率的方法

yipeiwu_com5年前Python基础

第一招:蛇打七寸:定位瓶颈

首先,第一步是定位瓶颈。举个简单的栗子,一个函数可以从1秒优化到到0.9秒,另一个函数可以从1分钟优化到30秒,如果要花的代价相同,而且时间限制只能搞定一个,搞哪个?根据短板原理,当然选第二个啦。

一个有经验的程序员在这里一定会迟疑一下,等等?函数?这么说,还要考虑调用次数?如果第一个函数在整个程序中需要被调用100000次,第二个函数在整个程序中被调用1次,这个就不一定了。举这个栗子,是想说明,程序的瓶颈有的时候不一定一眼能看出来。还是上面那个选择,程序员的你应该有感觉的,大多数情况下:一个「可以」从一分钟优化到30秒的函数会比一个「可以」从1秒优化到0.9秒的函数更容易捕获我们的注意,因为有很大的进步空间嘛。

所以,这么多废话讲完,献上第一招,profile。这是 python 自带的定位程序瓶颈的利器!虽然它提供了三种选项profile,cProfile,hotshot。还分为内置和外置。但是,个人觉得一种足矣,外置cProfile。心法如下:

python -m profile 逗比程序.py

这招的效果会输出一系列东西,比如函数被调用了几次,总时间多少,其中有多少是这个函数的子函数花费的,每次花多少时间,等等。嘛一图胜千言:

filename:lineno(function): 文件名:第几行(函数名)
ncalls: 这货一共调用了几次
tottime: 这货自己总共花了多少时间,也就是要除掉内部函数小弟们的花费
percall: 平均每次调用花的时间,tottime 除以 ncalls
cumtime: 这货还有它的所有内部函数小弟们的总花费
percall: 跟上面那个 percall 差不多,不过是 cumtime 除以 ncalls
找到最值得优化的点,然后干吧。

第二招:一蛇禅:只需一招

记得刚开始接触 Python 的时候,有一位学长告诉我,Python 有一个牛逼的理想,它希望每一个用它的人能写出一模一样的程序。Python 之禅有云:

There should be one-- and preferably only one --obvious way to do it

所以 Python 系专业的禅师提供了一些常用功能的 only one 的写法。本人看了一下传说中的PythonWiKi:PerformanceTips,总结了几个「不要酱紫」「要酱紫」。

合并字符串的时候不要酱紫:

s = "" for substring in list: s += substring

要酱紫:

s = "".join(slist)

格式化字符串的时候不要酱紫:

out = "<html>" + head + prologue + query + tail + "</html>"

要酱紫:

out = "<html>%s%s%s%s</html>" % (head, prologue, query, tail)

可以不用循环的时候就不要用循环,比如不要酱紫:

newlist = [] for word in oldlist: newlist.append(word.upper())

要酱紫:

newlist = map(str.upper, oldlist)

或者酱紫:

newlist = [s.upper() for s in oldlist]

字典初始化,比较常用的:

wdict = {} for word in words: if word not in wdict: wdict[word] = 0 wdict[word] += 1

如果重复的 word 太多了的话,可以考虑用酱紫的模式来省掉大量判断:

wdict = {} for word in words: try: wdict[word] += 1 except KeyError: wdict[word] = 1

尽量减少 function 调用次数,用内部循环代替,比如,不要酱紫:

x = 0 def doit1(i): global x x = x + i list = range(100000) t = time.time() for i in list: doit1(i)

要酱紫:

x = 0 def doit2(list): global x for i in list: x = x + i list = range(100000) t = time.time() doit2(list)

第三招:蛇之狙击:高速搜索

这一招部分来源于IBM:Python 代码性能优化技巧,搜索算法的最高境界是O(1)的算法复杂度。也就是 Hash Table。本人幸本科的时候学了点数据结构。知道 Python 的 list 使用类似链表的方法实现的。如过列表很大的话,在茫茫多的项里面用 if X in list_a 来做搜索和判断效率是非常低的。

Python 的 tuple 我用得非常少,不评论。另两个我用得非常多的是 set 和 dict。这两个就是用的类似 Hash Table 的实现方法。

所以尽量不要酱紫:

k = [10,20,30,40,50,60,70,80,90] for i in xrange(10000): if i in k: #Do something continue

要酱紫:

``` k = [10,20,30,40,50,60,70,80,90] k_dict = {i:0 for i in k}

先把 list 转换成 dictionary

for i in xrange(10000): if i in k_dict: #Do something continue ```

找 list 的交集,不要酱紫:

list_a = [1,2,3,4,5]
list_b = [4,5,6,7,8]
list_common = [a for a in list_a if a in list_b]

要酱紫:

list_a = [1,2,3,4,5]
list_b = [4,5,6,7,8]
list_common = set(list_a)&set(list_b)

第四招:小蛇蛇……:想不出来名字了,就是各种小 Tips

变量交换不需要中间变量:a,b = b,a (这里有个神坑,至今记忆深刻:True,False = False,True)
如果使用 Python2.x,用 xrange 代替 range,如果用 Python3.x,range 已经是 xrange 了,xrange 已经木有了。xrange 不会像 range 一样生成一个列表,而是生成一个迭代器,省内存。
可以用 x>y>z 代替 x>y and y>z。效率更高,可读性也更好。当然理论上 x>y
add(x,y) 一般会比 a+b 要快?这个本人有所怀疑,实验了一下,首先 add 不能直接用,要 import operator,第二,我的实验结果表示 add(x,y) 完全没有 a+b 快,更何况还要牺牲可读性。
while 1 确实比 while True 要快那么一点点。做了两次实验,大概快了15%左右。
第五招:无蛇胜有蛇:代码之外的性能

代码之外嘛,除了硬件之外,就是编译器了,这里隆重推荐 pypy。pypy是一种叫做 just-in-time 的即时编译器。这个编译器的特点就是编译一句跑一句,和静态的编译器的区别嘛,我在知乎上看到一个非常形象的比喻:

假定你是一个导演,静态编译就是让演员把整个剧本背下来吃透,然后连续表演一个小时。动态编译就是让演员表演两分钟,然后思考一下,再看一下剧本,再表演两分钟……

动态编译和静态编译各有所长,看你演的是电影还是话剧了。

此外还有一个 Cython 可以在 python 里内置一些 C 的代码。我用的非常少,但是关键时刻确实有效。

相关文章

python实现任意位置文件分割的实例

应用场景 在嵌入式开发中,常常需要将一个binary文件分割成多个文件,或者将一个binary的某块区域抓成一个单独文件。本篇blog以python为例,实现了以上需求; 实现代码...

原生python实现knn分类算法

原生python实现knn分类算法

一、题目要求 用原生Python实现knn分类算法。 二、题目分析 数据来源:鸢尾花数据集(见附录Iris.txt) 数据集包含150个数据集,分为3类,分别是:Iris Setosa(...

Python列表对象实现原理详解

Python列表对象实现原理详解

Python中的列表基于PyListObject实现,列表支持元素的插入、删除、更新操作,因此PyListObject是一个变长对象(列表的长度随着元素的增加和删除而变长和变短),同时它...

tensorflow学习笔记之mnist的卷积神经网络实例

mnist的卷积神经网络例子和上一篇博文中的神经网络例子大部分是相同的。但是CNN层数要多一些,网络模型需要自己来构建。 程序比较复杂,我就分成几个部分来叙述。 首先,下载并加载数据:...

名片管理系统python版

本文实例为大家分享了python名片管理系统的具体代码,供大家参考,具体内容如下 import os list_all = [] def page(): """输出主页面"""...