python编码最佳实践之总结


Posted in Python onFebruary 14, 2016

相信用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),显然数据的存储方式选择会直接影响算法的复杂度。

一、数据结构的选择
1. 在列表中查找:

 对于已经排序的列表考虑用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', 'a', 'b']
res = set(seq)

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

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

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

collections有三种类型:

deque:增强功能的类似list类型
defaultdict:类似dict类型
namedtuple:类似tuple类型

       列表是基于数组实现的,而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提倡简单、优雅。所以正确的语法实践又很有必要,这样才会写出优雅易于阅读的代码。

二、语法最佳实践
字符串操作:优于python字符串对象是不可改变的,因此对任何字符串的操作如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串,因此这种持续的 copy会在一定程度上影响Python的性能:
        (1)用join代替 '+' 操作符,后者有copy开销;

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

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

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

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, 2, 4, 6, 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), (1, 2), (1, 3), (1, 4), (2, 1), (2, 2), (2, 3), (2, 4), (3, 1), (3, 2), (3, 3), (3, 4)]
[0, 2, 4, 6, 8]
[0, 2, 4, 6, 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, args, kw))
  #hashlib是一个提供MD5和sh1的一个库,该结果保存在一个全局字典中
  return hashlib.sha1(key).hexdigest()

def memoize(duration=10):
  def _memoize(function):
    def __memoize(*args, **kw):
      key = compute_key(function, args, 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, b, c):
  return a + b + c

print very_very_complex_stuff(2, 2, 2)
print very_very_complex_stuff(2, 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必须对其自省好好理解。

三、 编码小技巧
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元素:

      x, y = y, x  # elegant and efficient

 而不是:

      temp = x
      x = y
      y = 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实现:

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的同学,优化性能不是重点,高效解决问题,让自己写的代码更加易于维护!

Python 相关文章推荐
python类和函数中使用静态变量的方法
May 09 Python
解读python logging模块的使用方法
Apr 17 Python
python线程池threadpool实现篇
Apr 27 Python
python 简单照相机调用系统摄像头实现方法 pygame
Aug 03 Python
Python常见数据类型转换操作示例
May 08 Python
python通过opencv实现图片裁剪原理解析
Jan 19 Python
python安装和pycharm环境搭建设置方法
May 27 Python
如何清空python的变量
Jul 05 Python
python接入支付宝的实例操作
Jul 20 Python
Python加速程序运行的方法
Jul 29 Python
详解基于python的全局与局部序列比对的实现(DNA)
Oct 07 Python
Python数据模型与Python对象模型的相关总结
Jan 26 Python
Python在Console下显示文本进度条的方法
Feb 14 #Python
如何使用python爬取csdn博客访问量
Feb 14 #Python
python动态网页批量爬取
Feb 14 #Python
Python ftp上传文件
Feb 13 #Python
Python cx_freeze打包工具处理问题思路及解决办法
Feb 13 #Python
Python批量创建迅雷任务及创建多个文件
Feb 13 #Python
Python 中 Meta Classes详解
Feb 13 #Python
You might like
PHP读取汉字的点阵数据
2015/06/22 PHP
PHP实现简单用户登录界面
2019/10/23 PHP
PHP 实现链式操作
2021/03/09 PHP
JQUERY 对象与DOM对象之两者相互间的转换
2009/04/27 Javascript
jquery及原生js获取select下拉框选中的值示例
2013/10/25 Javascript
jquery控制display属性为none或block
2014/03/31 Javascript
JQuery中属性过滤选择器用法实例分析
2015/05/18 Javascript
浅谈JavaScript的Polymer框架中的behaviors对象
2015/07/29 Javascript
谈谈Jquery中的children find 的区别有哪些
2015/10/19 Javascript
深入学习JavaScript的AngularJS框架中指令的使用方法
2016/03/05 Javascript
快速获取/设置iframe内对象元素的几种js实现方法
2016/05/20 Javascript
javascript运算符——位运算符全面介绍
2016/07/14 Javascript
JS 实现 ajax 异步浏览器兼容问题
2017/01/21 Javascript
canvas实现图像放大镜
2017/02/06 Javascript
vue+VeeValidate 校验范围实例详解(部分校验,全部校验)
2018/10/19 Javascript
vue项目每30秒刷新1次接口的实现方法
2018/12/04 Javascript
node.js连接mysql与基本用法示例
2019/01/05 Javascript
JS实现数组去重,显示重复元素及个数的方法示例
2019/01/21 Javascript
js cavans实现静态滚动弹幕
2020/05/21 Javascript
详解React 条件渲染
2020/07/08 Javascript
微信小程序实现页面监听自定义组件的触发事件
2020/11/01 Javascript
[03:09]显微镜下的DOTA2第一期——带你走进华丽的DOTA2世界
2014/06/20 DOTA
Windows系统下安装Python的SSH模块教程
2015/02/05 Python
详解Python2.x中对Unicode编码的使用
2015/04/03 Python
Python基础中所出现的异常报错总结
2016/11/19 Python
python操作excel文件并输出txt文件的实例
2018/07/10 Python
python制作朋友圈九宫格图片
2019/11/03 Python
PHP面试题附答案
2015/11/28 面试题
大专生自我鉴定范文
2013/10/01 职场文书
离婚财产处理协议书
2014/09/30 职场文书
学生抄袭作业的检讨书
2014/10/02 职场文书
商业用房租赁协议书
2014/10/13 职场文书
综合素质评价个性与发展自我评价
2015/03/06 职场文书
公积金贷款承诺书
2015/04/30 职场文书
教师个人教学反思
2016/02/23 职场文书
Python实现生成bmp图像的方法
2021/06/13 Python