Python编程速度技巧
maojj
|
1#
maojj 发表于 2007-08-21 13:07
Python编程速度技巧1. Python编程速度技巧 1.1. 最常见 一个最常见的速度陷坑(至少是俺在没看到网上这篇介绍时陷进去 过好些次的) 是: 许多短字串并成长字串时, 大家通常会用: Toggle line numbers 1 shortStrs = [ str0, str1, ..., strN] 2 #N+1个字串所组成的数列 3 longStr = '' 4 for s in shortStrs: longStr += s 因为Python里字串是不可变的, 所以每次 longStr += s 都是将原 来的 longStr 与 str 拷贝成一个新字串, 再赋给longStr. 随着 longStr的不断增长, 所要拷贝的内容越来越长. 最后导至str0被 拷贝N+1次, str1是N次, ... . 那咋办呢 ? 咱们来看看Skip Montanaro先生的解说: http://musi-cal.mojam.com/~skip/python/fastpython.html 及可参考一下Guido van Rossum本人的: http://www.python.org/doc/essays/list2str.html 1.1.1. 找出速度瓶颈
模块: Toggle line numbers 1 import profile 2 profile.run ('想要检查的函数名()') 就会打印出那个函数里调用了几次其它函数, 各用了多少时间, 总共用了多少时间等信息 --- Nice ? 详请参阅>中的 profile模块的论述. 当然脑袋笨一点或是聪明一点的, 也可以用time模块中的time() 来显示系统时间, 减去上次的time()就是与它的间隔秒数了. 1.1.2. 字串相并
Toggle line numbers 1 longStr =''.join(shortStrs) 立马搞定, 但如果shortStrs里面不都是字串, 而包含了些数 字呢 ? 直接用join就会出错. 不怕, 这样来: Toggle line numbers 1 shortStrs = [str(s) for s in shortStrs] 2 longStr = ''.join(shortStrs) 也即先将数列中所有内容都转化为字串, 再用join. 对少数几个字串相并, 应避免用: all = str0 + str1 + str2 + str3 而用: all = '%s%s%s%s' % (str0, str1, str2, str3) 1.1.3. 数列排序
你可以按特定的函数来: list.sort( 函数 ), 只要这个函数接受 两参数, 并按特定规则返回1, 0, -1就可以. --- 很方便吧? 但 会大大减慢运行速度. 下面的方法, 俺举例子来说明可能更容易 明白. 比方说你的数列是 l = ['az', 'by'], 你想以第二个字母来排序. 先取出你的关键词, 并与每个字串组成一个元组: new = map (lambda s: (s[1], s), l ) 于是new变成[('z', 'az'), ('y', 'by')], 再把new排一下序: new.sort() 则new就变成 [('y', 'by'), ('z', 'az')], 再返回每个元组中 的第二个字串: sorted = map (lambda t: t[1], new) 于是sorted 就是: ['by', 'az']了. 这里的lambda与map用得很 好.
1.1.4. 循环 比如for循环. 当循环体很简单时, 则循环的调用前头(overhead) 会显得很臃肿, 此时map又可以帮忙了. 比如你想把一个长数列 l=['a', 'b', ...]中的每个字串变成大写, 可能会用: Toggle line numbers 1 import string 2 newL = [] 3 for s in l: newL.append( string.upper(s) ) 用map就可以省去for循环的前头: Toggle line numbers 1 import string 2 newL = map (string.upper, l) Guido的文章讲得很详细. 1.1.5. 局域变量 及 '.' 象上面, 若用 append = newL.append, 及换种import方法: Toggle line numbers 1 import string 2 append = newL.append 3 for s in l: append (string.upper(s)) 会比在for中运行newL.append快一些, 为啥? 局域变量容易寻找. 俺自己就不比较时间了, Skip Montanaro的结果是: 基本循环: 3.47秒 去点用局域变量: 1.79秒 使用map: 0.54秒 1.1.6. try的使用 比如你想计算一个字串数列: l = ['I', 'You', 'Python', 'Perl', ...] 中每个词出现的次数, 你可能会: Toggle line numbers 1 count = {} 2 for s in l: 3 if not count.has_key(s): count[s] = 0 4 else: count[s] += 1 由于每次都得在count中寻找是否已有同名关键词, 会很费时间. 而用try: Toggle line numbers 1 count ={} 2 for s in l: 3 try: count[s] += 1 4 except KeyError: count[s] = 0 就好得多. 当然若经常出现例外时, 就不要用try了. 1.1.7. import语句 这好理解. 就是避免在函数定义中来import一个模块, 应全在 全局块中来import 1.1.8. 大量数据处理 由于Python中的函数调用前头(overhead)比较重, 所以处理大量 数据时, 应: Toggle line numbers 1 def f(): 2 for d in hugeData: ... 3 f() 而不要: Toggle line numbers 1 def f(d): ... 2 for d in hugeData: f(d) 这点好象对其它语言也适用, 差不多是放之四海而皆准, 不过对 解释性语言就更重要了. 1.1.9. 减少周期性检查 这是Python的本征功能: 周期性检查有没有其它绪(thread)或系 统信号(signal)等要处理. 可以用sys模块中的setcheckinterval 来设置每次检查的时间间隔. 缺省是10, 即每10个虚拟指令 (virtual instruction)检查一次. 当你不用绪并且也懒得搭理 系统信号时, 将检查周期设长会增加速度, 有时还会很显著. ---编/译完毕. 看来Python是易学难精了, 象围棋? 2. 我们自个儿的体悟 请有心得者分享!
其实setdefault方法就是为这个目的设的: Toggle line numbers 1 count = {} 2 for s in l: 3 count.setdefault(s, 0) += 1 这个其实能做更多。通常遇到的问题是要把类似的东西group起来,所以你可能想用: Toggle line numbers 1 count = {} 2 for s in l: 3 count.setdefault(s, []).append(s) 但是这样你只能把同样的东西hash起来,而不是一类东西。比如说你有一个dict构成的list叫sequence,需要按这些dict的某个key value分类,你还要对分类后的每个类别里面的这些dict各作一定的操作,你就需要用到Raymond实现的这个 groupby ,你就可以写: totals = dict((key, group) for key, group in groupby(sequence, lambda x: x.get('Age'))) |