Python2与Python3-过滤器行为的差异

Python2与Python3-过滤器行为的差异,python,python-3.x,python-2.7,Python,Python 3.x,Python 2.7,有人能帮助我理解为什么下面实现“Eratosthenes筛”的代码在Python2和Python3中的行为不同 l = range(2, 20) for i in range(2, 6): l = filter(lambda x: x == i or x % i != 0, l) print(tuple(l)) 使用Python 2.7: > python filter.py (2, 3, 5, 7, 11, 13, 17, 19) 使用Python 3.6: > pyt

有人能帮助我理解为什么下面实现“Eratosthenes筛”的代码在Python2和Python3中的行为不同

l = range(2, 20)
for i in range(2, 6):
    l = filter(lambda x: x == i or x % i != 0, l)
print(tuple(l))
使用Python 2.7:

> python filter.py
(2, 3, 5, 7, 11, 13, 17, 19)
使用Python 3.6:

> python filter.py
(2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18, 19)

我知道Python3的过滤器返回一个过滤器对象,但不能解释最终结果。(代码来自本lambdas教程)。

这里有两个部分起作用:

  • 在中,
    filter
    充当生成器:过滤是惰性完成的;及
  • lambda x:…
    中的
    i
    将被更新,
    for
    循环中的
    i
    也将进行升级
因此,最终您构建的是:

l = filter(lambda x: x == 5 or x % 5 != 0,
        filter(lambda x: x == 5 or x % 5 != 0,
            filter(lambda x: x == 5 or x % 5 != 0,
                filter(lambda x: x == 5 or x % 5 != 0,l)
            )
        )
    )
请注意,所有过滤都是像
i
一直是
5
一样进行的。因此,现在您调用tuple(..),实际的过滤将完成,正如您所看到的,只有五个不是五个的倍数被过滤掉

一个简单的解决方法是在循环中使用
列表
,以便
过滤器
自动完成:

l = range(2, 20)
for i in range(2, 6):
    l = list(filter(lambda x: x == i or x % i != 0, l))
print(tuple(l))
在python中运行此命令将返回:

>>> l = range(2, 20)
>>> for i in range(2, 6):
...     l = list(filter(lambda x: x == i or x % i != 0, l))
... 
>>> print(l)
[2, 3, 5, 7, 11, 13, 17, 19]
请注意,尽管这些语言看起来非常相同,但实际上是相互不兼容的“不同”语言:用一种语言编写的运行代码并不总是在另一种语言中工作,反之亦然

另一个注意事项(归功于@ShadowRanger)是一个可以在你的lambda中绑定
i
。您可以通过创建“高阶lambda”来实现这一点。而不是写:

lambda x : x == i or x % i != 0
你写道:

(lambda j : (lambda x : x == j or x % j != 0))(i)

所发生的事情是定义一个函数,该函数将实际接受
i
值的
j
作为输入。通过立即调用它,
j
绑定到Python-3
filter
中的
i

的值,返回一个生成器(在Python-2中返回一个列表),因此在使用它时对其进行评估。但这本身不是问题,问题是你的
i
发生了变化。在您使用
过滤器时,您的
i=5
和所有
过滤器
只需检查一下

我包括一些
打印
-语句,以便您可以更轻松地了解正在发生的事情:

l = range(2, 20)
for i in range(2, 6):
    l = filter(lambda x: print(x, i) or (x == i or x % i != 0), l)
list(l)

2 5
2 5
2 5
2 5
3 5
3 5
3 5
3 5
4 5
4 5
4 5
4 5
5 5
5 5
5 5
5 5
6 5
6 5
6 5
6 5
7 5
7 5
7 5
7 5
8 5
8 5
8 5
8 5
9 5
9 5
9 5
9 5
10 5
11 5
11 5
11 5
11 5
12 5
12 5
12 5
12 5
13 5
13 5
13 5
13 5
14 5
14 5
14 5
14 5
15 5
16 5
16 5
16 5
16 5
17 5
17 5
17 5
17 5
18 5
18 5
18 5
18 5
19 5
19 5
19 5
19 5

[2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18, 19]
那可能不是你的本意。您可以将
i
绑定到lambda:

l = range(2, 20)
for i in range(2, 6):
    l = filter((lambda j: lambda x: print(x, j) or (x == j or x % j != 0))(i), l)
    # or
    # l = filter(lambda x, i=i: print(x, i) or (x == i or x % i != 0), l)
list(l)
2 2
2 3
2 4
2 5
3 2
3 3
3 4
3 5
4 2
5 2
5 3
5 4
5 5
6 2
7 2
7 3
7 4
7 5
8 2
9 2
9 3
10 2
11 2
11 3
11 4
11 5
12 2
13 2
13 3
13 4
13 5
14 2
15 2
15 3
16 2
17 2
17 3
17 4
17 5
18 2
19 2
19 3
19 4
19 5

[2, 3, 5, 7, 11, 13, 17, 19]
或者将您的
过滤器
-结果立即转换为
元组

l = range(2, 20)
for i in range(2, 6):
    l = tuple(filter(lambda x: x == i or x % i != 0, l))
print(l)
# (2, 3, 5, 7, 11, 13, 17, 19)

因为在Python-3.x中,`filter的行为就像一个生成器。@WillemVanOnsem您应该写一个完整的答案。这种问题是值得的。@Soviut:我正在研究它。注意,通过绑定
lambda
I
的值,可以在没有中间列表的情况下获得相同的结果,因此不会在每个循环中更新:
过滤器(lambda x,I=I:x==I或x%I!=0,l)
将默认参数设置为
i=i
在定义时绑定,因此它不会被循环的进程所修改。@ShadowRanger:这确实是一个很好的观点。我将编辑我的答案以包含这个。@ShadowRanger:我定义了一个高阶函数,因为它更清楚发生了什么。尽管如此,您的方法也很有效。这一切都与定义本地
i
有关。