lambda与列表理解表现

时间:2009-10-27 18:51:23

标签: python lambda set list-comprehension

我最近发布了一个使用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

因此,虽然我发现列表推导总体上更容易阅读,但至少在这个例子中似乎存在一些性能问题。

所以,有两个问题:

  1. 为什么将lambda等推到一边?

  2. 对于列表理解方式,是否有更高效的实现?如果不进行测试,您如何知道它更有效?我的意思是,由于额外的函数调用,lambda / map / filter应该效率较低,但它看起来效率更高。

10 个答案:

答案 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(),使用一次,然后丢弃。构建列表然后再次删除它的时间只是浪费了。 (编辑:并注意同时包含mapfilter的版本必须构建两个列表,一个由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库并使用它们重新实现已经在operatorfunctools等模块中的函数(并且更快)。

列表推导与lambda无关。它们等同于函数式语言的标准filtermap函数。 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本身并不重要。