那些你忽略的python性能杀手


Python #性能2012-11-15 09:14

python的性能和计算能力一直被吐槽,从未被超越,越是这样越应该反省平常在使用的过程当中应当注意哪些优化措施,能让我们的程序运行得更快。

总结一下自己平常在使用 python的过程当中所导致的性能杀死:

1.内置类型方面:

python 内部的变量是分为可变类型和不可变类型,这里说的可变与不可变不是说变量与常量的区别,而是关于对象的生命周期问题。

假设一个python 原生字符串:

_str = "Hello"
id(str) = 33738016 #随机值 http://yige.org/
_str += "World"
id(str) = 33738056

从上面可以看出, id() 值是变了,意味着什么?

id 值的改变意味着这个对象的内存地址改变,也就是说,执行的 += 操作是将原来的字符串拼接完成后拷贝到新的地址。

可以看到两次id值相减为40,也就是5个 byte

所以应该尽量避免过多的 += 操作

如果需要拼接很多字符串的话尽量使用格式化字符串方式或者使用 str的 join方法

s = "%s,%s" %(str_1, str_2)
s = "".join([str_1, str_2])

总结一下哪些内置类型是变化哪些是不变化的:

dict, tuple, set 是不变是,字符串,其他变量是变化的

如果一个dict 或者 set 里面包含字符串或者其他变量,对dict, set的操作不会引起自身变化,但是其引用对象是变化的

 

2.一些迭代对象

在python2.x 版本里面,很多地方会使用到 for e in range(...) 形式或者 for e in E.items() 形式

这里需要注意的是,在 range()或者 items() 等操作过程中。是会在中间过程生成一个临时的 list

如果这个list很大的话将会让你的程序陷入噩梦

解决办法就是在这种for 循环中,使用可迭代对象

range 可由 xrange 替代,items 可由 iteritems 替代,还有诸如 iterkeys等

 

3.类对象

python的函数调用是很消耗资源的,所以尽量避免过多的函数调用,也可以适当使用lambda表达式替换

如果某个类需要创建大量的实例,而这个实例在运行过程中不会动态的添加其他的属性,那么可以使用内置的

__slot__方式

在类中定义了 __slot__ = ['attr1', 'attr2'] 

表示这个类只能拥有这些属性,这样避免在类生成过程中会为类自己生成 __dict__属性,这样可以减少很多的资源消耗

尽量少使用类变量,每个函数完成自己的功能,适当让耦合度降低

 

4.循环优化

多数的计算都发生在循环过程中,所以计算瓶颈可能出现在循环的地方。将循环适当的优化可以做到事半功倍的效果

while 循环使用 while 1: 而不是 while True:

使用 while True python也会自动给你转化为 while 1,避免每次循环都要转化一次岂不是更好

将计算长度移到循环外

很多同学喜欢这样使用:

for x in range(0, len( L )):
    do(x)

这样使用的时候会导致每一次循环都计算一次长度,浪费了很多

如果要生成的结果是一个list 的话,最好的方式不是使用每次将满足条件的值 append 到这个list

这样使用可以减少很多资源消耗

return [ func(e) for e in L ] 

5.一些其他的操作

对于字典的操作,获取一个值最好使用内置 get() 而不是按照下标来使用

否则需要写成这样:

try:
    X = d[key]
except ValueError :
    X = None

或者
if d.has_key(key):
   X =  d[key]
else:
   X =  None

这样的代码谁都不喜欢看到  

在需要使用到 in 操作的地方,使用 set 或者 dict 而不是 list

这样只需要O(1)时间就可以判断,而 list 会挨个比较

在可能需要对list 里面的值做排序的地方尽量使用 bisect

没有看过 bisect 的实现,内部是用的 C模块,估计使用的是红黑树,虽然插入操作需要的时间多一些,但是对于排序号的列表,后期优势就很大了

在使用到多线程的地方,对一个列表的操作使用 collections.deque()

collections.deque 支持多线程的原子操作,可以减少你自己加锁解锁的代码复杂性

在需要读取文件的操作中,使用迭代对象对文件的每一行做操作而不是用 readlines() 或者一次性读取后按行操作

使用方式做好是这样:

with open("file", "rb") as fp:
 
     for line in fp:
 
          do(line)

with 语句2.x版本在文件开头引入  

from __future__ import with_statement 

能想到的就是这些了,欢迎补充。作者: Email:zhangbo1@ijinshan.com QQ:513364476


相关文章

粤ICP备11097351号-1