我最近发布了一个使用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应该效率较低,但它看起来效率更高。

有帮助吗?

解决方案

你的测试做的事情非常不同。 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 之间的差异。

其他提示

当我修复你的代码以便列表理解和对 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表达式逐渐被淘汰的原因是许多人认为它们比列表推导更难以理解。我有点不情愿地同意。

问:为什么将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()获取iterable并逐个从中拉取值,并按照它进行求和,直到消耗掉所有值。这是在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有一些惯用的方法可以解决同样的问题,这些问题同时表现更好,更容易阅读和理解。

很多人已经指出你正在比较苹果和橘子等等。但我认为没有人展示如何进行一个非常简单的比较 - 列表理解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是相当独裁和血腥的;-)至少与其慢慢失去时尚一样多,因为它的性能成本。但后者非常真实。

首先,像这样测试:

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'构造相提并论。

集合是正确的解决方案。但是,请尝试交换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稍快一点

你的列表理解和lambda正在做不同的事情,匹配lambda的列表理解将是 [如果是S中的val,则为val中的val

效率不是列表理解首选的原因(而实际上它们在几乎所有情况下都略快)。他们首选的原因是可读性。

尝试使用较小的循环体和较大的循环,例如make T a set,并迭代S.在这种情况下,在我的机器上,列表理解几乎快两倍。

您的分析错误。看看 timeit模块,然后重试。

lambda 定义匿名函数。他们的主要问题是许多人不知道整个python库并使用它们来重新实现已经在 operator functools 等模块中的函数(以及快点 )。

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

这很快:

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

简单:减少比较,减少时间。

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

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

你可以从LC优化中获益:

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

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

lambda本身并不重要。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top