乐趣区

关于python:不懂性能优化再强的计算机也白玩

Python 的优良引人注目,不过说的性能,还真比不了 Java、C、Go,有没有晋升性能的技巧或办法呢?明天咱们一起学习下晋升 Python 性能的形式办法,那还等啥,来吧

局部变量更好
记得刚开始学习 C 语言时,对先定义再应用,感到很苦楚,常常因为申明问题编译不通

当初用 Python,变量随用随定义,爽到不行

不过我却养成了先定义在应用的习惯,例如:

a = None  # 能够不写
if some_condition:
    a = 10
else:
    a = 0

尽管 a = None 能够不写,然而还是习惯性的写解决做变量申明,相似 C 中的 int a;

这样的习惯,促使我在写代码之前,会先思考如何将会用到的变量,从而,对变量的应用范畴做出了严格的限定,能是部分的,绝不全局

而这个习惯进步程序的性能同时,还有其余益处

  • 局部变量查找的速度更快,因为 Python 是从代码块里,向外查找变量的,外面找不到,才会去里面找,最初才是全局变量,其余语言也一样
  • 局部变量更节俭内存,当代码块被执行,代码块中申明的局部变量所占用的内存就会被开释
  • 让代码更简洁,更易懂,比方能够用局部变量为简短的命名空间中的变量起别名,如 ls = os.linesp, 前面就在能够用 ls 简洁示意 os.linesp 了

函数虽好 尽量少调
函数是个平凡的创造,将能够被重复使用的过程集中起来,不便重复调用,而且函数的呈现,使递归得以实现

不过,调用函数的工夫老本比个别语句高的多,这是因为,函数的调用须要计算机做更多的调度协调工作

因而,应该尽量减少调用函数,特地是在大的循环中,更要留神

上面,列出几个典型例子,在这些状况下,能够不必调用函数

  • 应用 isinstance 代替 type,因为 Python 是面向对象语言,反对对象的继承,isinstance 能够间接检测对象的基类,不会像 type 一样对对象做全面的检测,会比 isinstance 做更多的函数调用
  • 防止在循环判断中,调用函数
# 每次循环都须要计算 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,最初只须要遍历一遍数组(书上理论应该的是字符串,一个字节索引示意一个数字),就能失去数据排序了

不言而喻,相比判断,索引效率更高

例如,应该尽量避免第一种写法,而用第二种:

# 判断并赋值
if a == 1:
    b = 10
elif a == 2:
    b = 20
...

# 利用索引,间接存值,性能更好
d = {1:10,2:20,...}
b = d[a]

迭代器的性能更优
Python 中有很多迭代器,不便咱们做各种循环

对于能够反对迭代器的对象,应用迭代器获取元素,比用索引获取元素的效率更高

例如:

a = [1, 2, 3]

for i in range(len(a)):  # 应用索引获取元素
    print(a[i])

for item in a:  # 应用迭代器获取元素
    print(item)

下面代码中,间接应用迭代器的效率更高

如果最开始接触的语言是 Python,应该比拟习惯间接应用迭代器,如果从其余语言转过来,可能更习惯应用索引

另外,如果须要在循环中失去每个元素的索引,能够通过一个索引计数器来实现:

a = ['a', 'b', 'c']
index = 0  # 初始化索引
for item in a:
    print(index, item)
    index += 1  # 递增索引值

提早享受是美德
已经有个驰名的心理学试验 —— 棉花糖试验,测试一群小孩子的提早满足能力,最终的论断是:提早满足能力强的孩子,将来胜利的机率更高

这尽管是对人的测试,但对计算机也实用,不过,背地的逻辑有些不同

对计算机而言,没必要将还用不到的内容加载到内存里,内存就好比咱们的工作太,如果放了太的的货色,查找就比拟艰难,从而影响工作效率

Python 中提供多种提早加载的性能,其中生成器是个典型的利用

以 list 容器为例,在应用该容器迭代一组数据时,必须当时将所有数据存储到容器中,能力开始迭代

而 生成器 却不同,它能够实现在迭代的同时生成元素,也就是不是一次性生成所有元素,只有在应用时才会生成

上面是个数字 生成器 的例子

def initNum():
    for i in range(10):
        yield i

gen = initNum()  # 失去生成器

for item in gen: # 遍历生成器
    print(item)

调用 initNum 会返回一个生成器,在 for 循环中,或者调用 next(等同于 gen.__next__()) 时才会生成下一个数字,在调用之前,不会生成所有的数据

生成器 占用的资源更少,意味着效率更高

先编译再调用
网络上有很多形容产品经理和研发间接矛盾的段子,让人哭笑不得

最次要的起因是,需要的不确定性和研发须要的确定性是互相矛盾的

面对不到变动的需要,研发须要一直调整,因而效率不会高

雷同的情理,计算机执行曾经编译好的程序,比执行边解析边执行的程序效率高很多

例如,C 的程序运行效率会更高,因为须要对 C 代码,编译后能力运行

咱们在享受 Python 这类动静语言带来的便利性同时,尽量让程序执行曾经编译好的代码,以便晋升性能

例如:

code = "for i in range(3):\n\tprint(i)"

exec(code)  # 间接执行

c = compile(code, "","exec")
exec(c)  # 编译后再执行

更常见的是正则表达式

line = "name\t\tage\tschool"

reObj = re.compile("\t+")  # 编译
reObj.split(line)  # 应用编译后的性能更好

编译后不仅性能更好,而且在能够重复应用时,进一步提供效率

模块化
咱们人类在一直了解这个世界的同时,产生了大量的信息和常识,始终无论哪个人,究其毕生也无奈把握所有的常识

于是迷信倒退为分科之学,将常识分门别类,以便不同的人把握理解他所关注的一点

常识是这样,咱们的单干也是如此,没有一个能够做所有的事件,须要多人相互配合,各负其责

计算机是咱们大脑的延长,是用咱们的思考形式、思维习惯制作的

面对大规模代码时,须要对代码进行分门别类,不仅不便咱们人类查看,还能晋升程序执行效率,能够在须要时,才去加载和执行模块和办法

例如:

# module1.py

def fun(alist):
    for item in alist:
        do some thing
# 测试用
a = [1, 2, 3]
fun(a)

定义了函数 fun,之后测试了下,如果其余代码援用了 fun: from module1 import fun

这测试用的代码就会被执行

好的形式是,将测试用代码封装起来:

# module1.py

def fun(alist):
    for item in alist:
        do some thing

if __name__ == "__main__":
    # 测试用
    a = [1, 2, 3]
    fun(a)

这样就能够防止测试用代码的无意义执行,从而晋升运行效率

总结
尽管当初的计算机性能很强,编程语言进步的性能很多,为咱们进步了极大的便当,然而良好的编程习惯和编码标准依然是很重要的,首先代码更多的时候是写给人看的,另外再强的计算机性能,也解决不了思维懈怠者的低效代码,就好比 你永远叫不醒一个装睡的人 一样。

业精于勤,在日常的工作和编程中,多学多练多思考,会使编程程度的晋升事倍功半,看似不起眼的小技巧,处处做好了,将带来微小的差别,这就是与高手间接的差距

以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈 ,发送“J”即可收费获取,每日干货分享

退出移动版