17

我最近使用 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 所以我评论了 print stmt out (除了最后一种方式它是无序的),但是在重复测试中产生的时间差异很有趣,如这个例子所示:

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 的效率应该较低,但它似乎更有效率。

保罗

4

10 回答 10

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.

于 2009-10-27T19:05:46.857 回答
24

当我修复您的代码以使列表理解和调用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 表达式被逐步淘汰的原因是许多人认为它们的可读性远低于列表推导式。我有点不情愿地同意了。

于 2009-10-27T19:19:22.973 回答
18

问:为什么 lambda 等被推到一边?

答:列表推导式和生成器表达式通常被认为是功能和可读性的完美结合。您使用map()reduce()filter()with 函数(通常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(), 并reduce()结合lambda函数,你可以做很多强大的事情。但是 Python 有解决相同问题的惯用方法,这些方法同时性能更好,更容易阅读和理解。

于 2009-10-27T21:15:51.290 回答
7

许多人已经指出,您正在将苹果与橙子等进行比较,等等。但我认为没有人展示过如何进行真正简单的比较——列表理解与地图加 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 可能不太清楚,或者它与斯巴达的奇怪联系(斯巴达人有一个 Lambda,代表“Lakedaimon”,画在他们的盾牌上——这表明 lambda 非常独裁和血腥;-)至少有很大程度上与它逐渐过时有关,因为它的性能成本。但后者是相当真实的。

于 2009-10-27T20:32:44.497 回答
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”构造相当。

于 2009-10-27T19:08:58.063 回答
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 略快

于 2009-10-27T19:08:31.313 回答
1

您的列表理解和 lambda 正在做不同的事情,与 lambda 匹配的列表理解将是[val for val in T if val in S].

效率并不是首选列表理解的原因(虽然它们实际上在几乎所有情况下都稍微快一些)。首选它们的原因是可读性。

尝试使用较小的循环体和较大的循环,例如将 T 设为一个集合,然后迭代 S。在这种情况下,在我的机器上,列表理解的速度几乎是原来的两倍。

于 2009-10-27T19:04:52.957 回答
1

你的分析做错了。查看timeit 模块并重试。

lambda定义匿名函数。他们的主要问题是许多人不了解整个 python 库并使用它们来重新实现已经在operator, functoolsetc 模块中的函数(而且速度更快)。

列表推导与 . 无关lambda。它们等价于函数式语言的标准filtermap函数。LC 是首选,因为它们也可以用作生成器,更不用说可读性了。

于 2009-10-27T19:12:38.823 回答
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

简单地说:更少的比较,更少的时间。

于 2009-10-27T19:15:10.613 回答
0

如果您必须处理过滤后的结果,列表推导可以产生更大的影响。在您的情况下,您只需构建一个列表,但如果您必须执行以下操作:

n = [f(i) for i in S if some_condition(i)]

您将从 LC 优化中获益:

n = map(f, filter(some_condition(i), S))

仅仅是因为后者必须构建一个中间列表(或元组,或字符串,取决于 S 的性质)。因此,您还会注意到对每种方法使用的内存的不同影响,LC 将保持较低。

lambda 本身并不重要。

于 2009-10-27T19:54:35.507 回答