Python 的优秀有目共睹,不过说的性能,还真比不了 Java、C、Go,有没有提升性能的技巧或方法呢?今天我们一起学习下提升 Python 性能的方式方法,那还等啥,来吧
局部变量更好
记得刚开始学习 C 语言时,对先定义再使用,感到很痛苦,经常因为声明问题编译不通
现在用 Python,变量随用随定义,爽到不行
不过我却养成了先定义在使用的习惯,例如:
1 |
|
虽然 a = None
可以不写,但是还是习惯性的写处理做变量声明,类似 C 中的 int a;
这样的习惯,促使我在写代码之前,会先考虑如何将会用到的变量,从而,对变量的使用范围做出了严格的限定,能是局部的,绝不全局
而这个习惯提高程序的性能同时,还有其他好处
- 局部变量查找的速度更快,因为 Python 是从代码块里,向外查找变量的,里面找不到,才会去外面找,最后才是全局变量,其他语言也一样
- 局部变量更节省内存,当代码快被执行,代码块中声明的局部变量所占用的内存就会被释放
- 让代码更简洁,更易懂,比如可以用局部变量为冗长的命名空间中的变量起别名,如
ls = os.linesp
,后面就在可以用ls
简洁表示os.linesp
了
函数虽好 尽量少调
函数是个伟大的发明,将可以被重复使用的过程集中起来,方便反复调用,而且函数的出现,使递归得以实现
不过,调用函数的时间成本比一般语句高的多,这是因为,函数的调用需要计算机做更多的调度协调工作
因此,应该尽量减少调用函数,特别是在大的循环中,更要注意
下面,列出几个典型例子,在这些情况下,可以不用调用函数
- 使用
isinstance
代替type
,因为 Python 是面向对象语言,支持对象的继承,isinstance
可以直接检测对象的基类,不会像type
一样对对象做全面的检测,会比isinstance
做更多的函数调用 -
避免在循环判断中,调用函数
1
2
3
4
5
6
7
8# 每次循环都需要计算 a 的长度 while i < len(a): statement # 先计算出 a 的程度,避免每次循环计算 length = len(a) while i < length: statement
- 如果模块 X 中有个 Y 对象或函数,那么最后这样引入
from X import Y
,而不是import X
,后者每次使用 Y 时,需要通过X.Y
的方式,比直接使用Y
多了一次函数调用
映射优于判断
在《编程珠玑 第二版》 第一章开篇中,描述了一个需求,需要对记录了一千多万行 7 位数据的文件中的数据排序,而且需要在很短时间内,在只使用 1M 内存的条件下完成
对于使用着现代计算机的我们来说,简直不可思议
一方面,现在的计算机动辄好几 G,几核,性能超强
另一方面,随便一个编程语言都有内置的高效排序算法
但在当时,计算机最大内存才不过几兆(M)!
你可能不会相信,就在当时的条件下,能在数十秒内完成吧
核心原理就是借用索引,来表示数值,比如 1000 是否存在,就看数组中索引为 1000 的值是否为 1,不存在则为 0,最后只需要便利一遍数组(书上实际应该的是字符串,一个字节索引表示一个数字),就能得到数据排序了
显而易见,相比判断,索引效率更高
例如,应该尽量避免第一种写法,而用第二种:
1 |
|
迭代器的性能更优
Python 中有很多迭代器,方便我们做各种循环
对于可以支持迭代器的对象,使用迭代器获取元素,比用索引获取元素的效率更高
例如:
1 |
|
上面代码中,直接使用迭代器的效率更高
如果最开始接触的语言是 Python,应该比较习惯直接使用迭代器,如果从其他语言转过来,可能更习惯使用索引
另外,如果需要在循环中得到每个元素的索引,可以通过一个索引计数器来实现:
1 |
|
延迟享受是美德
曾经有个著名的心理学实验 —— 棉花糖实验,测试一群小孩子的延迟满足能力,最终的结论是:延迟满足能力强的孩子,未来成功的机率更高
这虽然是对人的测试,但对计算机也适用,不过,背后的逻辑有些不同
对计算机而言,没必要将还用不到的内容加载到内存里,内存就好比我们的工作太,如果放了太的的东西,查找就比较困难,从而影响工作效率
Python 中提供多种延迟加载的功能,其中生成器是个典型的应用
以 list
容器为例,在使用该容器迭代一组数据时,必须事先将所有数据存储到容器中,才能开始迭代
而 生成器
却不同,它可以实现在迭代的同时生成元素,也就是不是一次性生成所元素,只有在使用时才会生成
下面是个数字 生成器
的例子
1 |
|
调用 initNum
会返回一个生成器,在 for
循环中,或者调用 next
(等同于 gen.__next__()
) 时才会生成下一个数字,在调用之前,不会生成所有的数据
生成器
占用的资源更少,意味着效率更高
先编译再调用
网络上有很多描述产品经理和研发直接矛盾的段子,让人啼笑皆非
最主要的原因是,需求的不确定性和研发需要的确定性是相互矛盾的
面对不到变动的需求,研发需要不断调整,因此效率不会高
相同的道理,计算机执行已经编译好的程序,比执行边解析边执行的程序效率高很多
例如,C 的程序运行效率会更高,因为需要对 C 代码,编译后才能运行
我们在享受 Python 这类动态语言带来的便利性同时,尽量让程序执行已经编译好的代码,以便提升性能
例如:
1 |
|
更常见的是正则表达式
1 |
|
编译后不仅性能更好,而且在可以反复使用时,进一步提供效率
模块化
我们人类在不断理解这个世界的同时,产生了大量的信息和知识,一直无论哪个人,究其一生也无法掌握所有的知识
于是科学发展为分科之学,将知识分门别类,以便不同的人掌握了解他所关注的一点
知识是这样,我们的合作也是如此,没有一个可以做所有的事情,需要多人相互配合,各负其责
计算机是我们大脑的延伸,是用我们的思考方式、思维习惯制造的
面对大规模代码时,需要对代码进行分门别类,着不仅方便我们人类查看,还能提升程序执行效率,可以在需要时,才去加载和执行模块和方法
例如:
1 |
|
定义了函数 fun
,之后测试了下,如果其他代码引用了 fun
: from module1 import fun
这测试用的代码就会被执行
好的方式是,将测试用代码封装起来:
1 |
|
这样就可以避免测试用代码的无意义执行,从而提升运行效率
总结
虽然现在的计算机性能很强,编程语言提高的功能很多,为我们提高了极大的便利,但是良好的编程习惯和编码规范仍然是很重要的,首先代码更多的时候是写给人看的,另外在强的计算机性能,也解决不了思维懒惰者的低效代码,就好比 你永远叫不醒一个装睡的人 一样。
业精于勤,在日常的工作和编程中,多学多练多思考,会使编程水平的提升事半功倍,看似不起眼的小技巧,处处做好了,将带来巨大的差异,这就是与高手直接的差距
期望今天分享的一点小技巧能给您一丝启发,让您在通往高手的道路上更加顺畅,比心!
参考
- https://www.tutorialdocs.com/article/7-habits-to-improve-python-programs.html
- https://stackoverflow.com/questions/1549801/what-are-the-differences-between-type-and-isinstance
- http://c.biancheng.net/view/2393.html
- https://www.cnblogs.com/nomorewzx/p/4203829.html
- https://book.douban.com/subject/3227098/
- https://baike.baidu.com/item/%E6%A3%89%E8%8A%B1%E7%B3%96%E5%AE%9E%E9%AA%8C/15659236