如何做一个更好的Python开发者(1)?(Python Performance Tips, Part 1译)

OO~ posted @ 2013年3月30日 10:32 in python , 6746 阅读

    从最开始的写python代码到现在,其实还没有对如何更好的开发python代码有一个全新的认识,这片文章是我翻译一个大牛中的大牛给Python写的tips。正文如下:

    如果你想要阅读Zen of Python,只需要在你的Python解释器里键入import this。对于刚刚接触python的人,如果心细,就会注意到“解释器”这个词,随即也会意识到Python是另一种脚本语言。“它肯定很慢!”这种观点毫无疑问是正确的:Python程序不像编译语言那样高效。甚至Python的倡导者也会告诉你Pyhton语言在效率方面处于劣势。然而,YouTube已经证明,Python在每个小时内能提供4000万视频的服务。你必须做的所有事情就是写出高效的代码,在速度要求方面寻找C/C++的外部实现。这里有一些tips可以帮助你成为一个更好的Python开发者:

1. 选择内建函数:
你可以使用Python写高效的代码,但是你却很难避免用到内建函数(用C语言完成的)。点击这里察看。这写内建函数非常快。

2. 使用join()函数将大量的字符串连起来:
你能使用符号“+”将几个字符串结合起来。因为字符串是不可改变的,每个涉及到“+”的操作都会创建一个新的字符串,同时拷贝旧字符串的内容。一种应用频度非常高的惯用语法是利用Python的数组模式修改单个字符;接着就是使用join()函数再创建你最终的字符串。

#This is good to glue a large number of strings
for chunk in input():
    my_string.join(chunk)

3. 在交换变量值中使用多重赋值:
这种方式在Python中是非常快捷的:

x, y = y, x

而下面这种方式要慢很多:

temp = x
x = y
y = temp

4. 尽可能使用临时变量:
Python检索临时变量的速度要比检索全局变量快。所以,尽量避免使用“global”这个关键词。

5. 尽可能的使用“in”:
一般在检查成员关系时,会用到关键词“in”。这种方式很简洁,也很快捷。

for key in sequence:
    print "found"

6. 通过懒惰方式的importing提高速度:
将“import”语句移到函数中去,这样你就只会在用到的时候import某些内容。也就是说,如果有些模块你不是马上用到,你就可以晚些import它们。例如,在启动的时候,你可以先不import一长列的模块来提高你代码的速度。这个技术没有加强全局的效率。但是它帮助你将导入模块的时间更加均匀的分布在代码中。

7. 在无限循环中使用“while 1”:
有时候你会在你的代码中使用到无限循环。(例如,一个监听socket)虽然“while True”实现了同样的功能,但是,“while 1”是一个单独的跳操作。你可以将这个trick用在你高效的Python代码中。

while 1:
    #do stuff, faster with while 1
while True:
    #do stuff, slower with while True

8. 使用list内涵:
在Python2.0之后,你就可以使用list内涵代替很多“for”和“while”块。list内涵更快的原因在于,在循环的过程中,Python解释器能最优的发现一个可预测的模式。一方面,list内涵更加易读(函数编程中),另一方面,它为你节约了一个额外的计数变量。例如,我们可以在一行内得到1到10之间的偶数数值:

#the good way to iterate a range
evens = [i fo i in range(10) if i % 2 == 0]
[0, 2, 4, 6, 8]
#the following is not so Pythonic
i = 0
evens = []
while i < 10:
    if i % 2 == 0: evens.append(i)
    i += 1
[0, 2, 4, 6, 8]

9. 在每个长序列中使用xrange():
这样做可以为你节约大量的系统内存空间,因为xrange()每次只在一个序列中产生一个整数元素。和range()相反,它给出你整个列表,而这在整体的循环中是不必要的。

10. 根据需求,使用Python生成器得到相应的数值:
这种做法同样可以节约内存空间,提高代码效率。如果你在传输视频的数据流,你能send一个chunk的字节,而不是整个数据流。例如,

chunk = (1000 * i for i in xrange(1000))
chunk
<generator object <genexpr> at 0x7f65d90dcaa0>
chunk.next()
0
chunk.next()
1000
chunk.next()
2000

11. 学习itertools模块:
这个模块在迭代和聚合方面非常高效。通过三行代码我们就可以得到list[1, 2, 3]的所有排列:

import itertools
iter = itertools.permutations([1, 2, 3])
list(iter)
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2 ,1)]

12. 学习bisect模块将list保持有序:
这是一个开源的二叉查找实现,同时也是一个很快的有序序列插入工具。也就是说,你能这样使用:

improt bisect
bisect.insort(list, element)

通过上面的操作,你就在list中插入了一个元素,接下来你也不需要再次调用sort()函数来保持有序,我们也必须注意到后者在长序列中的代价是非常高的。

13. 需要理解,一个Pyhon列表其实就是一个数组:
Python中的列表实现和人们在Computer science中谈到的一般独立链接列表是不一样的。Python的列表其实是一个数组。这样一来,你就可以以O(1)的时间,利用下标索引,很快搜索到列表中的一个元素,而不需要从列表的开始处搜索。这点意味着什么呢?一个Python的开发者在使用insert()函数插入一个list元素时需要三思。例如:list.insert(0, element)
在list的首部插入一个元素并不是一个高效的选择,因为这个list中所有的其他下标都会改变。然而你可以将一个元素高效的添加到list尾部,利用函数list.append()。如果你确实想要更快的插入或者删除两端的元素,最好选择双端队列deque。因为deque在Python中是作为双向链表来实现的,所以它会更快一些。这里不再做过多的赘述。

14. 使用dict和set检测成员关系:
Python的dict和set在检查一个元素是否存在时非常快捷。因为这两者是利用hash表来实现的。它们的查找时间可以达到O(1)。因此,如果你要经常检查成员关系,最好使用dict或者set。

mylist = ['a', 'b', 'c'] #Slower, check membership with list:
'c' in mylist
True
myset = set(['a', 'b', 'c']) #Faster, check membership with set:
'c' in myset:
True

15. 在sort()函数中兼并使用Schwartzian Transform:
list.sort()函数在处理数据方面是非常快的。它会将一个list处理得到一个自然顺序的列表。然而,你有时候想要得到非自然序列的list。例如,你想要将IP地址根据你服务器的位置来排序。Python支持传统的比较,也就是说你可以使用list.sort(cmp())函数来实现,但是这样做会比list.sort()慢很多,因为你引入该函数调用了上层的内容。如果速度需要考虑进来,你可以使用在Schwartzian Transform算法的基础之上产生的Guttman-Rosler Transform算法。可以读到其具体如何实现的算法当然是非常有趣的,这里我们对其如何工作的只做一个简单的总结,你可以通过调用Python的内建函数list.sort()快速转换list,而不需要使用相对时间较慢的list.sort(cmp())函数。

16. 使用Python的decorator缓存结果:
符号“@”是Python的点缀(decorator)语法。不仅仅可以使用它来跟踪,上锁or记录日志。你甚至可以点缀Python的函数,使其记住接下来会使用到的结果。这个技术被称之为memoization。接下来有一个例子:

from functools import wraps
def memo(f):
    cache = {}
    @wraps(f)
    def wrap(*arg):
	if arg not in cache: cache['arg'] = f(*arg)
	return cache['arg']
    return wrap

我们也可以将点缀用在Fibonacci函数中

@memo
def fib(i):
    if i < 2: return 1
    return fib(i - 1) + fib(i - 2)

这里的主要目的很简单:增强(or decorate)你的函数,使其记住已经计算得到的每个Fibonacci元素。如果这些值都在缓存中,也就没有必要再次计算。

17. 理解Python的GIL(全局解释器锁global interpreter lock):
GIL是非常必要的,因为CPython的内存处理并不具有线程安全性。你不能简单的创建多个线程,然后希望Python会在多核机器上跑的非常快。这是因为GIL将会防止多个本地线程一次执行Python的多个字节码。也就是说,GIL会将你的所有线程系列化。然而,你可以通过使用多线程协调几个已创建的且独立运行于Python代码之外的进程,加速代码运行效率。

18. 将Python源代码作为你的文档:
Python有很多考虑到速度而使用C语言实现的模块。当效率在编码中非常重要时,官方的文档就会显得匮乏,此时你可以自由的去体验源代码。而在这个过程中你会发现很多潜在的数据结构和算法。Python仓库是一个非常美妙的地方,也很值得在其中徜徉:http://svn.python.org/view/python/trunk/Modules

结语:
世界上没有大脑的替代物。程序开发者的责任是深刻的看清楚问题,达到不会轻易的将一个坏的构思集合在一起的境界。这篇文章里关于Python的tips能帮助你获得更好的效率。如果速度仍然不是足够的优,此时Python需要寻求额外的帮助:编写以及运行外部扩展代码。我们将会在这片文章的第二部份涉及到这一块。
To be continued...

(原文链接:http://blog.monitis.com/index.php/2012/02/13/python-performance-tips-part-1/)

Avatar_small
OO~ 说:
2013年3月30日 10:35

看来我了解的还是比较少的,感觉有几个点不是很清楚,都没有接触到过!接下的时间要加油!!

Avatar_small
依云 说:
2013年3月30日 12:11

在 Python 3 中,while True 和 while 1 是一样的。所以在 Python 3 中应当使用前者在提高可读性的时候不失性能。

Avatar_small
OO~ 说:
2013年3月30日 23:11

哦,这样啊,我还没进行具体的研究呢!多谢楼主补充啦~

Avatar_small
laven 说:
2013年4月19日 20:01

最近想折腾折腾python,特来求IDE推荐

Avatar_small
依云 说:
2013年4月19日 21:56

@laven: vimer 路过~

Avatar_small
OO~ 说:
2013年4月20日 14:52

@laven: 我就是在ubuntu下开发的,google关键字就可以配置环境了!一些简单的命令我是用python + vim
比较大型的,用的是eclipse+python

Avatar_small
OO~ 说:
2013年4月20日 14:52

命令->程序

Avatar_small
laven 说:
2013年4月21日 16:16

@依云:围观vimer~~

Avatar_small
laven 说:
2013年4月21日 16:17

好吧,忘记你不用windows了。。。。。。

Avatar_small
OO~ 说:
2013年4月21日 19:34

在windows下,还是推荐你是用PyDev for Eclipse,pydev是一个功能强大且易用的 Eclipse Python IDE 插件,便于以后开发大型项目,所以直接使用,上手之后,以后更容易往下走

Avatar_small
laven 说:
2013年4月21日 20:17

@OO~:嗯嗯


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter