python 最佳实践总结

来源:http://www.atatech.org/article/detail/9463/0?ticket=0ec08e00-4a5a-4b2b-b601-62ec01a3876d

一、数据结构的选择:

     1. 在列表中查找:

   对于已经排序的列表考虑用bisect模块来实现查找元素,该模块将使用二分查找实现

1 def find(seq, el) :
2     pos = bisect(seq, el)
3     if pos == 0 or ( pos == len(seq) and seq[-1] != el ) :
4         return -1
5     return pos - 1

    而快速插入一个元素可以用:

1 bisect.insort(list, element)

这样就插入元素并且不需要再次调用 sort() 来保序,要知道对于长list代价很高.

    2. set代替列表: 

    比如要对一个list进行去重,最容易想到的实现:

1 seq = ['a''a''b']
2 res = []
3 for in seq:
4     if not in res:
5         res.append(i)

显然上面的实现的复杂度是O(n2),若改成:

1 seq = ['a''a''b']
2 res = set(seq)

复杂度马上降为O(n),当然这里假定set可以满足后续使用。

另外,set的union,intersection,difference等操作要比列表的迭代快的多,因此如果涉及到求列表交集,并集或者差集等问题可以转换为set来进行,平时使用的时候多注意下,特别当列表比较大的时候,性能的影响就更大。

    3. 使用python的collections模块替代内建容器类型:

collections有三种类型:

  1. deque:增强功能的类似list类型
  2. defaultdict:类似dict类型
  3. namedtuple:类似tuple类型

       列表是基于数组实现的,而deque是基于双链表的,所以后者在中间or前面插入元素,或者删除元素都会快很多。 

       defaultdict为新的键值添加了一个默认的工厂,可以避免编写一个额外的测试来初始化映射条目,比dict.setdefault更高效,引用python文档的一个例子:

#使用profile stats工具进行性能分析

01 >>> from pbp.scripts.profiler import profile, stats
02 >>> s = [('yellow'1), ('blue'2), ('yellow'3),
03 ... ('blue'4), ('red'1)]
04 >>> @profile('defaultdict')
05 ... def faster():
06 ... d = defaultdict(list)
07 ... for k, v in s:
08 ... d[k].append(v)
09 ...
10 >>> @profile('dict')
11 ... def slower():
12 ... d = {}
13 ... for k, v in s:
14 ... d.setdefault(k, []).append(v)
15 ...
16 >>> slower(); faster()
17 Optimization: Solutions
18 306 ]
19 >>> stats['dict']
20 {'stones'16.587882671716077'memory'396,
21 'time': <strong>0.35166311264038086</strong>}
22 >>> stats['defaultdict']
23 {'stones'6.5733464259021686'memory'552,
24 'time': <strong>0.13935494422912598</strong>}

可见性能提升了快3倍。defaultdict用一个list工厂作为参数,同样可用于内建类型,比如long等。

 

除了实现的算法、架构之外,python提倡简单、优雅。所以正确的语法实践又很有必要,这样才会写出优雅易于阅读的代码。

二、语法最佳实践:

  1. 字符串操作:优于python字符串对象是不可改变的,因此对任何字符串的操作如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串,因此这种持续的 copy会在一定程度上影响Python的性能:

        (1)用join代替 '+' 操作符,后者有copy开销;

        (2)同时当对字符串可以使用正则表达式或者内置函数来处理的时候,选择内置函数。如str.isalpha(),str.isdigit(),str.startswith((‘x’, ‘yz’)),str.endswith((‘x’, ‘yz’))

        (3)字符格式化操作优于直接串联读取:

1 str = "%s%s%s%s" % (a, b, c, d)  # efficient
2 str = "" + a + b + c + d + ""  # slow

    2. 善用list comprehension(列表解析)  & generator(生成器) & decorators(装饰器),熟悉itertools等模块

       (1) 列表解析,我觉得是python2中最让我印象深刻的特性,举例1:

01 >>> # the following is not so Pythonic 
02 >>> numbers = range(10)
03 >>> i = 0
04 >>> evens = []
05 >>> while i < len(numbers):
06 >>>    if %2 == 0: evens.append(i)
07 >>>    i += 1
08 >>> [02468]
09  
10 >>> # the good way to iterate a range, elegant and efficient
11 >>> evens = [ i for in range(10if i%2 == 0]
12 >>> [02468]

   举例2:

1 def _treament(pos, element):
2     return '%d: %s' % (pos, element)<br>
01 = open('test.txt''r')
02 if __name__ == '__main__':
03     #list comps 1
04     print sum(len(word) for line in for word in line.split())
05     #list comps 2
06     print [(x + 1, y + 1for in range(3for in range(4)]
07     #func
08     print filter(lambda x: x % 2 == 0range(10))
09     #list comps3
10     print [i for in range(10if % 2 == 0]
11     #list comps4 pythonic
12     print [_treament(i, el) for i, el in enumerate(range(10))]
13  
14 output:
15 24
16 [(11), (12), (13), (14), (21), (22), (23), (24), (31), (32), (33), (34)]
17 [02468]
18 [02468]
19 ['0: 0''1: 1''2: 2''3: 3''4: 4''5: 5''6: 6''7: 7''8: 8''9: 9']<br>

没错,就是这么优雅简单。

   (2) 生成器表达式在python2.2引入,它使用'lazy evaluation'思想,因此在使用内存上更有效。引用python核心编程中计算文件中最长的行的例子:

1 = open('/etc/motd, 'r')
2 longest = max(len(x.strip()) for in f)
3 f.close()
4 return longest

这种实现简洁而且不需要把文件文件所有行读入内存。

       (3) python在2.4引入装饰器,又是一个让人兴奋的特性,简单来说它使得函数和方法封装(接收一个函数并返回增强版本的函数)更容易阅读、理解。'@'符号是装饰器语法,你可以装饰一个函数,记住调用结果供后续使用,这种技术被称为memoization的,下面是用装饰器完成一个cache功能:

01 import time
02 import hashlib
03 import pickle
04 from itertools import chain
05 cache = {}
06 def is_obsolete(entry, duration):
07     return time.time() - entry['time'] > duration
08  
09 def compute_key(function, args, kw):
10     #序列化/反序列化一个对象,这里是用pickle模块对函数和参数对象进行序列化为一个hash值
11     key = pickle.dumps((function.func_name, args, kw))
12     #hashlib是一个提供MD5和sh1的一个库,该结果保存在一个全局字典中
13     return hashlib.sha1(key).hexdigest()
14  
15 def memoize(duration=10):
16     def _memoize(function):
17         def __memoize(*args, **kw):
18             key = compute_key(function, args, kw)
19  
20             # do we have it already
21             if (key in cache and
22                 not is_obsolete(cache[key], duration)):
23                 print 'we got a winner'
24                 return cache[key]['value']
25  
26             # computing
27             result = function(*args, **kw)
28             # storing the result
29             cache[key] = {'value': result,-
30                             'time': time.time()}
31             return result
32         return __memoize
33     return _memoize
34  
35 @memoize()
36 def very_very_complex_stuff(a, b, c):
37     return + + c
38  
39 print very_very_complex_stuff(222)
40 print very_very_complex_stuff(222)
41  
42  
43 @memoize(1)
44 def very_very_complex_stuff(a, b):
45     return + b
46  
47 print very_very_complex_stuff(22)
48 time.sleep(2)
49 print very_very_complex_stuff(22)
1 <br>

运行结果:

6

we got a winner

6

4

4

装饰器在很多场景用到,比如参数检查、锁同步、单元测试框架等,有兴趣的人可以自己进一步学习。

    3.  善用python强大的自省能力(属性和描述符):自从使用了python,真的是惊讶原来自省可以做的这么强大简单,关于这个话题,限于内容比较多,这里就不赘述,后续有时间单独做一个总结,学习python必须对其自省好好理解。

三、 编码小技巧:

  1. 在python3之前版本使用xrange代替range,因为range()直接返回完整的元素列表而xrange()在序列中每次调用只产生一个整数元素,开销小。(在python3中xrange不再存在,里面range提供一个可以 遍历任意长度的范围的iterator)
  2. if done is not None比语句if done != None更快;
  3. 尽量使用"in"操作符,简洁而快速: for i in seq: print i
  4. 'x < y < z'代替'x < y and y < z';
  5. while 1要比while True更快, 因为前者是单步运算,后者还需要计算;
  6. 尽量使用build-in的函数,因为这些函数往往很高效,比如add(a,b)要优于a+b;
  7. 在耗时较多的循环中,可以把函数的调用改为内联的方式,内循环应该保持简洁。
  8. 使用多重赋值来swap元素:

    1 x, y = y, x  # elegant and efficient

             而不是:

    1 temp = x
    2 = y
    3 = temp

      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实现:

1 switch case结构:
1 switch (var)
2 {
3     case v1: func1();
4     case v2: func2();
5     ...
6     case vN: funcN();
7     default: default_func();
8 }

dictionary实现:

1 values = {
2            v1: func1,
3            v2: func2,
4            ...
5            vN: funcN,
6          }
7 values.get(var, default_func)()

lambda实现:

1 {
2   '1'lambda: func1,
3   '2'lambda: func2,
4   '3'lambda: func3
5 }[value]()

用try…catch来实现带Default的情况,个人推荐使用dict的实现方法。

 

    这里只总结了一部分python的实践方法,希望这些建议可以帮助到每一位使用python的同学,优化性能不是重点,高效解决问题,让自己写的代码更加易于维护,更加pythonic!

你可能感兴趣的:(python 最佳实践总结)