python编码最佳实践之总结
相信用python的同学不少,本人也一直对python情有独钟,毫无疑问python作为一门解释性动态语言没有那些编译型语言高效,但是python简洁、易读以及可扩展性等特性使得它大受青睐。 工作中很多同事都在用python,但往往很少有人关注它的性能和惯用法,一般都是现学现用,毕竟python不是我们的主要语言,我们一般只是使用它来做一些系统管理的工作。但是我们为什么不做的更好呢?python zen中有这样一句:There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. 大意就是python鼓励使用一种最优的方法去完成一件事,这也是和ruby等的一个差异。所以一种好的python编写习惯个人认为很重要,本文就重点从性能角度出发对python的一些惯用法做一个简单总结,希望对大家有用~ 提到性能,最容易想到的是降低复杂度,一般可以通过测量代码回路复杂度(cyclomatic complexitly)和Landau符号(大O)来分析,比如dict查找是O(1),而列表的查找却是O(n),显然数据的存储方式选择会直接影响算法的复杂度。 一、数据结构的选择 对于已经排序的列表考虑用bisect模块来实现查找元素,该模块将使用二分查找实现 def find(seq,el) : pos = bisect(seq,el) if pos == 0 or ( pos == len(seq) and seq[-1] != el ) : return -1 return pos - 1 而快速插入一个元素可以用: bisect.insort(list,element) 这样就插入元素并且不需要再次调用 sort() 来保序,要知道对于长list代价很高. 2. set代替列表: 比如要对一个list进行去重,最容易想到的实现: seq = ['a','a','b'] res = [] for i in seq: if i not in res: res.append(i) 显然上面的实现的复杂度是O(n2),若改成: seq = ['a','b'] res = set(seq) 复杂度马上降为O(n),当然这里假定set可以满足后续使用。 另外,set的union,intersection,difference等操作要比列表的迭代快的多,因此如果涉及到求列表交集,并集或者差集等问题可以转换为set来进行,平时使用的时候多注意下,特别当列表比较大的时候,性能的影响就更大。 3. 使用python的collections模块替代内建容器类型: collections有三种类型: deque:增强功能的类似list类型 列表是基于数组实现的,而deque是基于双链表的,所以后者在中间or前面插入元素,或者删除元素都会快很多。 defaultdict为新的键值添加了一个默认的工厂,可以避免编写一个额外的测试来初始化映射条目,比dict.setdefault更高效,引用python文档的一个例子: #使用profile stats工具进行性能分析 >>> from pbp.scripts.profiler import profile,stats >>> s = [('yellow',1),('blue',2),('yellow',3),... ('blue',4),('red',1)] >>> @profile('defaultdict') ... def faster(): ... d = defaultdict(list) ... for k,v in s: ... d[k].append(v) ... >>> @profile('dict') ... def slower(): ... d = {} ... for k,v in s: ... d.setdefault(k,[]).append(v) ... >>> slower(); faster() Optimization: Solutions [ 306 ] >>> stats['dict'] {'stones': 16.587882671716077,'memory': 396,'time': 0.35166311264038086} >>> stats['defaultdict'] {'stones': 6.5733464259021686,'memory': 552,'time': 0.13935494422912598} 可见性能提升了快3倍。defaultdict用一个list工厂作为参数,同样可用于内建类型,比如long等。 除了实现的算法、架构之外,python提倡简单、优雅。所以正确的语法实践又很有必要,这样才会写出优雅易于阅读的代码。 二、语法最佳实践 (2)同时当对字符串可以使用正则表达式或者内置函数来处理的时候,选择内置函数。如str.isalpha(),str.isdigit(),str.startswith((‘x',‘yz')),str.endswith((‘x',‘yz')) (3)字符格式化操作优于直接串联读取: str = "%s%s%s%s" % (a,b,c,d) # efficient 2. 善用list comprehension(列表解析) & generator(生成器) & decorators(装饰器),熟悉itertools等模块: (1) 列表解析,我觉得是python2中最让我印象深刻的特性,举例1: >>> # the following is not so Pythonic >>> numbers = range(10) >>> i = 0 >>> evens = [] >>> while i < len(numbers): >>> if i %2 == 0: evens.append(i) >>> i += 1 >>> [0,2,4,6,8] >>> # the good way to iterate a range,elegant and efficient >>> evens = [ i for i in range(10) if i%2 == 0] >>> [0,8] 举例2: def _treament(pos,element): return '%d: %s' % (pos,element) f = open('test.txt','r') if __name__ == '__main__': #list comps 1 print sum(len(word) for line in f for word in line.split()) #list comps 2 print [(x + 1,y + 1) for x in range(3) for y in range(4)] #func print filter(lambda x: x % 2 == 0,range(10)) #list comps3 print [i for i in range(10) if i % 2 == 0] #list comps4 pythonic print [_treament(i,el) for i,el in enumerate(range(10))] output: 24 [(1,(1,(2,(3,4)] [0,8] [0,8] ['0: 0','1: 1','2: 2','3: 3','4: 4','5: 5','6: 6','7: 7','8: 8','9: 9'] 没错,就是这么优雅简单。 (2) 生成器表达式在python2.2引入,它使用'lazy evaluation'思想,因此在使用内存上更有效。引用python核心编程中计算文件中最长的行的例子: f = open('/etc/motd,'r') longest = max(len(x.strip()) for x in f) f.close() return longest 这种实现简洁而且不需要把文件文件所有行读入内存。 (3) python在2.4引入装饰器,又是一个让人兴奋的特性,简单来说它使得函数和方法封装(接收一个函数并返回增强版本的函数)更容易阅读、理解。'@'符号是装饰器语法,你可以装饰一个函数,记住调用结果供后续使用,这种技术被称为memoization的,下面是用装饰器完成一个cache功能: import time import hashlib import pickle from itertools import chain cache = {} def is_obsolete(entry,duration): return time.time() - entry['time'] > duration def compute_key(function,args,kw): #序列化/反序列化一个对象,这里是用pickle模块对函数和参数对象进行序列化为一个hash值 key = pickle.dumps((function.func_name,kw)) #hashlib是一个提供MD5和sh1的一个库,该结果保存在一个全局字典中 return hashlib.sha1(key).hexdigest() def memoize(duration=10): def _memoize(function): def __memoize(*args,**kw): key = compute_key(function,kw) # do we have it already if (key in cache and not is_obsolete(cache[key],duration)): print 'we got a winner' return cache[key]['value'] # computing result = function(*args,**kw) # storing the result cache[key] = {'value': result,- 'time': time.time()} return result return __memoize return _memoize @memoize() def very_very_complex_stuff(a,c): return a + b + c print very_very_complex_stuff(2,2) print very_very_complex_stuff(2,2) @memoize(1) def very_very_complex_stuff(a,b): return a + b print very_very_complex_stuff(2,2) time.sleep(2) print very_very_complex_stuff(2,2) 运行结果: 6 we got a winner 6 4 4 装饰器在很多场景用到,比如参数检查、锁同步、单元测试框架等,有兴趣的人可以自己进一步学习。 3. 善用python强大的自省能力(属性和描述符):自从使用了python,真的是惊讶原来自省可以做的这么强大简单,关于这个话题,限于内容比较多,这里就不赘述,后续有时间单独做一个总结,学习python必须对其自省好好理解。 三、 编码小技巧 x,y = y,x # elegant and efficient 而不是: temp = x 9. 三元操作符(python2.5后):V1 if X else V2,避免使用(X and V1) or V2,因为后者当V1=""时,就会有问题。 10. python之switch case实现:因为switch case语法完全可用if else代替,所以python就没 有switch case语法,但是我们可以用dictionary或lamda实现: switch case结构: switch (var) { case v1: func1(); case v2: func2(); ... case vN: funcN(); default: default_func(); } dictionary实现: values = { v1: func1,v2: func2,... vN: funcN,} values.get(var,default_func)() lambda实现: { '1': lambda: func1,'2': lambda: func2,'3': lambda: func3 }[value]() 用try…catch来实现带Default的情况,个人推荐使用dict的实现方法。 这里只总结了一部分python的实践方法,希望这些建议可以帮助到每一位使用python的同学,优化性能不是重点,高效解决问题,让自己写的代码更加易于维护! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |