在Python3中应用Python2代码时遇到问题

在Python3中应用Python2代码时遇到问题,python,filter,lambda,Python,Filter,Lambda,为了了解lambdas,我遵循了这一点,并运行了这个关于计算素数的示例(Python2.x): 印刷品 [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] [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,

为了了解lambdas,我遵循了这一点,并运行了这个关于计算素数的示例(Python2.x):

印刷品

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
[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]
但是,在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))
印刷品

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
[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]
我不明白为什么会有不同。我知道filter在Python3中返回一个filter对象(而不是一个列表),但据我所知,这不会影响结果

删除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]

我希望有人能给我一些启发,因为我对正在发生的事情很好奇。

这种行为是由两种因素共同作用的结果。一个是,在Python3中,
filter
(和
range
)返回一次生成一个值的对象,而不是预先计算所有值。另一个是,在Python2和Python3中,在封闭作用域中引用名称的函数都会在上创建闭包

在Python3版本中,在每次循环迭代中,使用函数(lambda)创建一个过滤器。因为过滤器是“惰性”的,所以它存储函数并在以后每当您请求过滤值时调用它。(在本例中,当您调用
list(nums)
)时,该函数引用了函数外部的变量
i
。因此,当
filter
调用该函数时,它会使用
i
的值调用该函数,该值在您获取过滤值时存在(即,当您调用
list(nums)
),而不是在您创建过滤器时存在。这就是为什么您的结果缺少所有7的倍数(7除外):7是
i
循环中的最后一个值,因此在调用它们时,所有lambda都在检查7的倍数

正如Bhargav Rao在评论中所说,复制Python 2行为的一种方法是将lambda更改为filter
list(nums)
,而不是
nums
。这将强制每个过滤器“清除”上一个过滤器,而不是等待在最后应用所有过滤器。(这实际上就是Python 2所做的,这就是为什么在Python 2中看不到这种行为。)

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

nums = filter(lambda x, i=i: x == i or x % i , list(nums))

使
i
成为lambda的参数,在每次循环迭代时“锁定”值。这意味着过滤器仍将延迟运行,但每个过滤器都将存储适当的值以进行过滤,因此即使您稍后调用它(在更改
i
之后),它仍将工作。

此行为是由两个因素组合而成的。一个是,在Python3中,
filter
(和
range
)返回一次生成一个值的对象,而不是预先计算所有值。另一个是,在Python2和Python3中,在封闭作用域中引用名称的函数都会在上创建闭包

在Python3版本中,在每次循环迭代中,使用函数(lambda)创建一个过滤器。因为过滤器是“惰性”的,所以它存储函数并在以后每当您请求过滤值时调用它。(在本例中,当您调用
list(nums)
)时,该函数引用了函数外部的变量
i
。因此,当
filter
调用该函数时,它会使用
i
的值调用该函数,该值在您获取过滤值时存在(即,当您调用
list(nums)
),而不是在您创建过滤器时存在。这就是为什么您的结果缺少所有7的倍数(7除外):7是
i
循环中的最后一个值,因此在调用它们时,所有lambda都在检查7的倍数

正如Bhargav Rao在评论中所说,复制Python 2行为的一种方法是将lambda更改为filter
list(nums)
,而不是
nums
。这将强制每个过滤器“清除”上一个过滤器,而不是等待在最后应用所有过滤器。(这实际上就是Python 2所做的,这就是为什么在Python 2中看不到这种行为。)

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

nums = filter(lambda x, i=i: x == i or x % i , list(nums))

使
i
成为lambda的参数,在每次循环迭代时“锁定”值。这意味着过滤器仍将惰性地运行,但每个过滤器都将存储适当的值以进行过滤,因此即使您稍后调用它(在更改
i
之后),它仍将工作。

范围
过滤器
返回Py3中的对象,而python2中的列表。@BhargavRao:这距离解释这种行为还有很长的路要走。@BrenBarn简单的更改,
filter(lambda x:x==i或x%i,list(nums))
将在Python3:)中工作,但我相信你可以解释得更好。@BhargavRao:距离解释这种行为还有很长的路要走。是的,我懒洋洋地在注释中评估答案:)
range
filter
返回Py3中的对象,而python2中的列表。@BhargavRao:这离解释这种行为还有很长的路要走。@BrenBarn简单更改,
filter(lambda x:x==I或x%I,list(nums))
将在python 3:)中工作,但我相信您可以更好地解释。@BhargavRao:这离解释行为还有很长的路要走。是的,我正在懒洋洋地在注释中评估答案:)很好的解释,这也是为什么
2to3
自动更改
过滤器的原因吗(在本例中)要一份清单吗?@BhargavRao:可能吧。我对2to3的工作原理知之甚少。我想这听起来像是另一个问题。无论如何,谢谢你的时间。很好的解释,这也是为什么
2to3
自动将
过滤器(在本例中)更改为列表理解的原因吗?@BhargavRao:可能。我对2to3的工作原理知之甚少。我想这听起来像是另一个问题。无论如何,谢谢你抽出时间。