相信用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
装饰器在很多场景用到,比如参数检查、锁同步、单元测试框架等,有兴趣的人可以自己进一步学习。
三、 编码小技巧
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的同学,优化性能不是重点,高效解决问题,让自己写的代码更加易于维护!
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。