我最近发布了一个使用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应该效率较低,但它看起来效率更高。
保
答案 0 :(得分:30)
你的测试做的事情非常不同。 S为1M元素,T为300:
[x for x in S for y in T if x==y]= 54.875
此选项执行300M相等比较。
filter(lambda x:x in S,T)= 0.391000032425
此选项通过S进行300次线性搜索。
[val for val in S if val in T]= 12.6089999676
此选项通过T进行1M线性搜索。
list(set(S) & set(T))= 0.125
此选项执行两组结构和一组交集。
这些选项之间的性能差异与每个人使用的算法更相关,而不是,而不是列表推导与lambda
之间的任何差异。
答案 1 :(得分:23)
当我修改你的代码以便列表理解和对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表达式逐渐被淘汰的原因是许多人认为它们比列表推导更难以理解。我有点不情愿地同意。
答案 2 :(得分:18)
问:为什么将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()
必须遍历整个列表。如果列表长达一百万个项目,并且第一个项目评估为真,那么这种性能差异就会很明显。顺便说一句,在Python 2.5中添加了any()
;如果你没有它,这里是旧版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()
和reduce()
与lambda
函数组合在一起,您可以做很多有力的事情。但是,Python有一些惯用的方法可以解决同样的问题,这些问题同时表现更好,更容易阅读和理解。
答案 3 :(得分:6)
很多人已经指出你正在比较苹果和橘子等等。但我认为没有人展示如何进行一个非常简单的比较 - 列表理解vs地图加上lambda,其他一点都没有阻碍 - - 那可能是:
$ python -mtimeit -s'L=range(1000)' 'map(lambda x: x+1, L)'
1000 loops, best of 3: 328 usec per loop
$ python -mtimeit -s'L=range(1000)' '[x+1 for x in L]'
10000 loops, best of 3: 129 usec per loop
在这里,您可以非常清楚地看到lambda的成本 - 大约200微秒,在这种操作足够简单的情况下,例如这个操作会淹没操作本身。
数字与过滤器非常相似,因为问题是不过滤器或地图,而是lambda本身:
$ python -mtimeit -s'L=range(1000)' '[x for x in L if not x%7]'
10000 loops, best of 3: 162 usec per loop
$ python -mtimeit -s'L=range(1000)' 'filter(lambda x: not x%7, L)'
1000 loops, best of 3: 334 usec per loop
毫无疑问,lambda可能不太清楚,或者它与斯巴达的奇怪联系(Spartans有一个Lambda,用于“Lakedaimon”,画在他们的盾牌上 - 这表明lambda是相当独裁和血腥的;-)至少与其慢慢失去时尚一样多,因为它的性能成本。但后者非常真实。
答案 4 :(得分:4)
首先,像这样测试:
import timeit
S=[x for x in range(10000)]
T=[y**2 for y in range(30)]
print "v1", timeit.Timer('[x for x in S for y in T if x==y]',
'from __main__ import S,T').timeit(100)
print "v2", timeit.Timer('filter(lambda x:x in S,T)',
'from __main__ import S,T').timeit(100)
print "v3", timeit.Timer('[val for val in T if val in S]',
'from __main__ import S,T').timeit(100)
print "v4", timeit.Timer('list(set(S) & set(T))',
'from __main__ import S,T').timeit(100)
基本上,每次测试时你都会做不同的事情。当您重写列表理解时,例如
[val for val in T if val in S]
性能将与'lambda / filter'构造相提并论。
答案 5 :(得分:2)
集合是正确的解决方案。但是,请尝试交换S和T,看看需要多长时间!
filter(lambda x:x in T,S)
$ python -m timeit -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' 'filter(lambda x:x in S,T)'
10 loops, best of 3: 485 msec per loop
$ python -m timeit -r1 -n1 -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' 'filter(lambda x:x in T,S)'
1 loops, best of 1: 19.6 sec per loop
所以你看到S和T的顺序非常重要
更改列表推导的顺序以匹配过滤器
$ python -m timeit -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' '[x for x in T if x in S]'
10 loops, best of 3: 441 msec per loop
因此,如果事实上列表理解比我计算机上的lambda稍快一点
答案 6 :(得分:1)
你的列表理解和lambda做了不同的事情,匹配lambda的列表理解将是[val for val in T if val in S]
。
效率不是列表理解是首选的原因(虽然它们实际上几乎在所有情况下都略快)。他们首选的原因是可读性。
尝试使用较小的循环体和较大的循环,例如make T a set,并迭代S.在这种情况下,在我的机器上,列表理解几乎快两倍。
答案 7 :(得分:1)
您的分析错误。看看timeit module然后重试。
lambda
定义了匿名函数。他们的主要问题是许多人不知道整个python库并使用它们重新实现已经在operator
,functools
等模块中的函数(并且更快)。
列表推导与lambda
无关。它们等同于函数式语言的标准filter
和map
函数。 LC是首选,因为它们也可以用作发生器,更不用说可读性了。
答案 8 :(得分:0)
这很快:
def binary_search(a, x, lo=0, hi=None):
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
midval = a[mid]
if midval < x:
lo = mid+1
elif midval > x:
hi = mid
else:
return mid
return -1
time1 = time.time()
N = [x for x in T if binary_search(S, x) >= 0]
time2 = time.time()
print 'time diff binary search=', time2-time1
简单:减少比较,减少时间。
答案 9 :(得分:0)
如果您必须处理过滤结果,列表推导可以产生更大的差异。在您的情况下,您只需构建一个列表,但如果您必须执行以下操作:
n = [f(i) for i in S if some_condition(i)]
你可以从LC优化中获益:
n = map(f, filter(some_condition(i), S))
只是因为后者必须构建一个中间列表(或元组或字符串,具体取决于S的性质)。因此,您还会注意到每种方法使用的内存会产生不同的影响,LC会保持较低的水平。
lambda本身并不重要。