在python 3中应用python 2代码的麻烦

Sco*_*ork 1 python lambda filter

要了解lambdas,我正在学习本教程,并遇到了关于计算素数的例子(python 2.x):

nums = range(2,50)
for i in range(2,8):
    nums = filter(lambda x: x == i or x % i, nums)

print (list(nums))
Run Code Online (Sandbox Code Playgroud)

版画

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Run Code Online (Sandbox Code Playgroud)

但是,在python 3.4中尝试这个时,它产生了意想不到的行为:

nums = range(2,50)
for i in range(2,8):
    nums = filter(lambda x: x == i or x % i , nums)

print(list(nums))
Run Code Online (Sandbox Code Playgroud)

版画

[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48]
Run Code Online (Sandbox Code Playgroud)

我不明白为什么会有区别.我知道过滤器在python 3(而不是列表)中返回一个过滤器对象,但据我所知,这不应该影响结果.

删除for循环会产生正确的结果:

>>> nums = range(2,50)
>>> nums = filter(lambda x: x == 2 or x % 2, nums)
>>> nums = filter(lambda x: x == 3 or x % 3, nums)
>>> nums = filter(lambda x: x == 4 or x % 4, nums)
>>> nums = filter(lambda x: x == 5 or x % 5, nums)
>>> nums = filter(lambda x: x == 6 or x % 6, nums)
>>> nums = filter(lambda x: x == 7 or x % 7, nums)
>>> print(list(nums))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
Run Code Online (Sandbox Code Playgroud)

我希望有人可以启发我,因为我很好奇正在发生的事情.

Bre*_*arn 5

这种行为是两件事的结合.一个是,在Python 3中,filter(和range)返回一次产生一个值的对象,而不是预先计算所有值.另一个是,在Python 2和3中,引用封闭范围中的名称的函数会创建对名称的闭包,而不是值.

在Python 3版本中,在每次循环迭代中,使用函数(lambda)创建过滤器.因为过滤器是"懒惰的",所以只要您要求过滤值,它就会存储该函数并稍后调用它.(在这种情况下,当你调用时list(nums).)但是该函数引用i了函数外部的变量.因此,当filter调用该函数时,它会i在您获得过滤值(即调用时list(nums))时使用该值调用它,而不是在创建过滤器时调用它.这就是为什么你的结果缺少7的所有倍数(7除外):7是你i循环中的最后一个值,所以你所有的lambdas都会在它们被调用时检查7的倍数.

重现Python 2行为的一种方法,正如Bhargav Rao在评论中所说,是将你的lambda改为过滤list(nums)而不是nums.这会强制每个过滤器"清除"前一个过滤器,而不是等到最后应用它们.(这实际上是Python 2所做的,这就是为什么你在Python 2中没有看到这种行为的原因.)

另一种方法是使用关于闭包的链接问题中描述的默认参数技巧.将循环体更改为:

nums = filter(lambda x, i=i: x == i or x % i , list(nums))
Run Code Online (Sandbox Code Playgroud)

使ilambda的参数"锁定"每个循环迭代的值.这意味着过滤器仍然会延迟运行,但每个过滤器都会存储适当的值以进行过滤,因此即使稍后调用它(更改后i),它仍然可以工作.