lambda与列表理解表现

Tal*_*aul 17 python lambda list-comprehension set

我最近发布了一个使用lambda函数的问题,并且在回复中有人提到lambda不受欢迎,而是使用list comprehensions.我对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
Run Code Online (Sandbox Code Playgroud)

它们都打印相同的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
Run Code Online (Sandbox Code Playgroud)

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

那么,有两个问题:

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

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

保罗

Gre*_*ill 30

你的测试做的事情非常不同.S为1M元素,T为300:

[x for x in S for y in T if x==y]= 54.875
Run Code Online (Sandbox Code Playgroud)

此选项执行300M相等比较.

 

filter(lambda x:x in S,T)= 0.391000032425
Run Code Online (Sandbox Code Playgroud)

此选项通过S进行300次线性搜索.

 

[val for val in S if val in T]= 12.6089999676
Run Code Online (Sandbox Code Playgroud)

此选项通过T进行1M线性搜索.

 

list(set(S) & set(T))= 0.125
Run Code Online (Sandbox Code Playgroud)

此选项执行两个集合构造和一个集合交集.


这些选项之间的性能差异与每个选项使用的算法有很大关系,不是列表推导与列表理解之间的任何差异lambda.

  • 前3个案例中的O是相同的 - O(n*m)= O(m*n)但是其中一些在Python中做了更多的工作而其他人正在做更多的工作. (2认同)

Omn*_*ous 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
Run Code Online (Sandbox Code Playgroud)

然后输出更像是:

time diff [x for x in T if x in S]= 0.414485931396
time diff filter(lambda x:x in S,T)= 0.466315984726
Run Code Online (Sandbox Code Playgroud)

所以列表理解的时间通常非常接近并且通常小于lambda表达式.

lambda表达式被逐步淘汰的原因是许多人认为它们比列表推导更不易读.我有点不情愿地同意.


ste*_*eha 18

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

答:列表推导和生成器表达式通常被认为是功能和可读性的良好组合.您使用map(),reduce()filter()使用函数(通常是lambda函数)的纯函数式编程风格被认为不那么清晰.此外,Python还添加了内置函数,可以很好地处理所有主要用途reduce().

假设你想要一个列表.这有两种方法.

lst = range(10)
print reduce(lambda x, y: x + y, lst)

print sum(lst)
Run Code Online (Sandbox Code Playgroud)

请注册我作为粉丝,sum()而不是reduce()解决此问题的粉丝.这是另一个类似的问题:

lst = range(10)
print reduce(lambda x, y: bool(x or y), lst)

print any(lst)
Run Code Online (Sandbox Code Playgroud)

any()解决方案不仅更容易理解,而且速度更快; 它有短路评估,一旦找到任何真正的价值就会停止评估.在reduce()具有通过整个列表杀青.如果列表长达一百万个项目,并且第一个项目评估为真,那么这种性能差异就会很明显.顺便any()说一下,在Python 2.5中添加了; 如果你没有它,这里是旧版Python的版本:

def any(iterable):
    for x in iterable:
        if x:
            return True
    return False
Run Code Online (Sandbox Code Playgroud)

假设您想从某个列表中创建偶数的平方列表.

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]
Run Code Online (Sandbox Code Playgroud)

现在假设您想要对该正方形列表求和.

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)
Run Code Online (Sandbox Code Playgroud)

生成器表达式实际上只返回一个可迭代对象. sum()获取可迭代并从中拉取值,逐个求和,直到所有值都被消耗.这是在Python中解决此问题的最有效方法.相比之下,map()解决方案以及在调用中具有列表理解的等效解决方案sum()必须首先构建一个列表; 然后将此列表传递给sum(),使用一次并丢弃.构建列表然后再次删除它的时间只是浪费了.(编辑:和注意,有两个版本,map并且filter必须建立2个列表,一个内置filter,一个由建map; 两个名单将被丢弃.)(编辑:但是在Python 3.0和更高版本,map()和过滤器()现在都"懒惰"并生成迭代器而不是列表;所以这一点不像以前那样真实.另外,在Python 2.x中你可以使用itertools.imap()和itertools.ifilter()来迭代 - 基于地图和过滤器.但我继续比任何地图/过滤器解决方案更喜欢生成器表达式解决方案.)

通过编写map(),filter()和功能reduce()相结合lambda,你可以做很多有力的事情.但是Python有一些惯用的方法来解决同样的问题,这些问题同时表现更好,更易于阅读和理解.


Ale*_*lli 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
Run Code Online (Sandbox Code Playgroud)

在这里,您可以非常清楚地看到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
Run Code Online (Sandbox Code Playgroud)

毫无疑问,lambda可能不太清楚,或者它与斯巴达的奇怪联系(Spartans有一个Lambda,用于"Lakedaimon",画在他们的盾牌上 - 这表明lambda是相当独裁和血腥的;-)至少有与其慢慢失去时尚有关,因为它的性能成本.但后者非常真实.