lambda与列表推导性能对比
我最近在用lambda函数发了个问题,有人回复说lambda函数不太受欢迎了,建议用列表推导式来代替。我对Python还比较陌生,于是我做了个简单的测试:
import time
S=[x for x in range(1000000)]
T=[y**2 for y in range(300)]
#
#
time1 = time.time()
N=[x for x in S for y in T if x==y]
time2 = time.time()
print 'time diff [x for x in S for y in T if x==y]=', time2-time1
#print N
#
#
time1 = time.time()
N=filter(lambda x:x in S,T)
time2 = time.time()
print 'time diff filter(lambda x:x in S,T)=', time2-time1
#print N
#
#
#http://snipt.net/voyeg3r/python-intersect-lists/
time1 = time.time()
N = [val for val in S if val in T]
time2 = time.time()
print 'time diff [val for val in S if val in T]=', time2-time1
#print N
#
#
time1 = time.time()
N= list(set(S) & set(T))
time2 = time.time()
print 'time diff list(set(S) & set(T))=', time2-time1
#print N #the results will be unordered as compared to the other ways!!!
#
#
time1 = time.time()
N=[]
for x in S:
for y in T:
if x==y:
N.append(x)
time2 = time.time()
print 'time diff using traditional for loop', time2-time1
#print N
它们都打印出相同的N,所以我把打印语句注释掉了(除了最后一种因为是无序的),不过在重复测试中,结果的时间差异很有趣,像这个例子所示:
time diff [x for x in S for y in T if x==y]= 54.875
time diff filter(lambda x:x in S,T)= 0.391000032425
time diff [val for val in S if val in T]= 12.6089999676
time diff list(set(S) & set(T))= 0.125
time diff using traditional for loop 54.7970001698
所以虽然我觉得列表推导式整体上更容易阅读,但在这个例子中似乎存在一些性能问题。
所以,我有两个问题:
为什么lambda等函数被逐渐抛弃了?
对于列表推导式,有没有更高效的实现方式?你怎么知道它更高效,而不需要测试呢?我意思是,lambda/map/filter本来应该因为额外的函数调用而效率低,但看起来反而更高效。
保罗
10 个回答
问:为什么 lambda 等功能逐渐被冷落了?
答:列表推导式和生成器表达式通常被认为是功能强大且易于理解的好方法。使用 map()
、reduce()
和 filter()
这些函数(通常是 lambda
函数)来进行纯函数式编程的风格,被认为不够清晰。此外,Python 还增加了一些内置函数,可以很好地处理 reduce()
的主要用途。
假设你想对一个列表求和,这里有两种方法可以做到。
lst = range(10)
print reduce(lambda x, y: x + y, lst)
print sum(lst)
我更喜欢用 sum()
来解决这个问题,而不喜欢用 reduce()
。接下来是另一个类似的问题:
lst = range(10)
print reduce(lambda x, y: bool(x or y), lst)
print any(lst)
不仅 any()
的解决方案更容易理解,而且速度也快得多;它采用短路求值的方式,一旦找到任何一个为真的值就会停止计算。而 reduce()
则必须遍历整个列表。如果列表有一百万个项目,而第一个项目就为真,这种性能差异会非常明显。顺便提一下,any()
是在 Python 2.5 中添加的;如果你没有这个功能,这里有一个适用于旧版本 Python 的版本:
def any(iterable):
for x in iterable:
if x:
return True
return False
假设你想从某个列表中生成偶数的平方列表。
lst = range(10)
print map(lambda x: x**2, filter(lambda x: x % 2 == 0, lst))
print [x**2 for x in lst if x % 2 == 0]
现在假设你想对这个平方列表求和。
lst = range(10)
print sum(map(lambda x: x**2, filter(lambda x: x % 2 == 0, lst)))
# list comprehension version of the above
print sum([x**2 for x in lst if x % 2 == 0])
# generator expression version; note the lack of '[' and ']'
print sum(x**2 for x in lst if x % 2 == 0)
生成器表达式实际上只是返回一个可迭代的对象。sum()
会从这个可迭代对象中逐个取值,并进行求和,直到所有值都被处理完。这是用 Python 解决这个问题的最有效方法。相比之下,map()
的解决方案,以及在调用 sum()
时使用列表推导式的等效方案,必须先构建一个列表;这个列表随后被传递给 sum()
,只使用一次,然后被丢弃。构建列表和再删除它的时间都是浪费的。(编辑:需要注意的是,使用 map
和 filter
的版本必须构建两个列表,一个是由 filter
构建的,另一个是由 map
构建的;这两个列表都会被丢弃。)(编辑:但在 Python 3.0 及更新版本中,map()
和 filter()
现在都是“惰性”的,产生的是迭代器而不是列表;所以这个问题不再像以前那么严重。此外,在 Python 2.x 中,你可以使用 itertools.imap() 和 itertools.ifilter() 来实现基于迭代器的 map 和 filter。不过我仍然更喜欢生成器表达式的解决方案,而不是任何 map/filter 的解决方案。)
通过组合 map()
、filter()
和 reduce()
以及 lambda
函数,你可以做很多强大的事情。但 Python 有一些习惯用法,可以同时更高效、更易读地解决同样的问题。
当我修正你的代码,让列表推导式和调用filter
实际上做同样的事情时,情况就大不相同了:
import time
S=[x for x in range(1000000)]
T=[y**2 for y in range(300)]
#
#
time1 = time.time()
N=[x for x in T if x in S]
time2 = time.time()
print 'time diff [x for x in T if x in S]=', time2-time1
#print N
#
#
time1 = time.time()
N=filter(lambda x:x in S,T)
time2 = time.time()
print 'time diff filter(lambda x:x in S,T)=', time2-time1
#print N
那么输出就更像是:
time diff [x for x in T if x in S]= 0.414485931396
time diff filter(lambda x:x in S,T)= 0.466315984726
所以列表推导式的运行时间通常和lambda表达式差不多,甚至通常会更少一些。
人们逐渐不再使用lambda表达式的原因是,很多人觉得它们比列表推导式更难懂。我有点不情愿地同意这一点。
你的测试在做的事情差别很大。这里有一个集合S,里面有100万个元素,而集合T只有300个元素:
[x for x in S for y in T if x==y]= 54.875
这个选项需要进行3亿次相等比较。
filter(lambda x:x in S,T)= 0.391000032425
这个选项需要在S中进行300次线性搜索。
[val for val in S if val in T]= 12.6089999676
这个选项需要在T中进行100万次线性搜索。
list(set(S) & set(T))= 0.125
这个选项需要构建两个集合,并进行一次集合交集操作。
这些选项之间性能的差异更多是因为它们使用的算法不同,而不是因为列表推导和lambda
之间有什么区别。