在 python 中实施 "Sieve of Eratosthenes" 时出现麻烦的过滤器行为

Troublesome filter behavior when implementing the "Sieve of Eratosthenes" in python

我为 "Sieve of Eratosthenes" 试验部门 变体想出了这个 Python 代码:

import itertools

def sieve():
    # begin with all natural numbers above 1
    picker = itertools.count(2)
    while True:
        # take the next available number
        v = next(picker)
        yield v
        # filter from the generator its multiples
        picker = filter(lambda x: x % v != 0, picker)

它没有像我预期的那样工作。

在调试它时,我得到一些行为,当 filter 被调用时我不明白:lambda 的 x 参数得到一个具体的参数,它是 [=19 的下一个元素=] 发电机。即使在查看 filter.

的文档后,我也不明白这种行为

运行

s = sieve()
for i in range(5):
    print(next(s))

我得到:

2
3
4
5
6

而不是

2
3
5
7
11

更新:

我的错误来自于对 lambda 在 Python 中的工作方式的误解,更多关于 here

向 lambda 添加一个额外的参数修复了这个问题:

picker = filter(lambda x, prime = v: x % prime != 0, picker)

我认为问题是因为您依赖于局部变量,而您创建的生成器(使用 filter())正在引用局部变量,当生成器继续迭代时这些变量将被覆盖。

如果您改为使用本地函数,则效果很好:

def sieve():
    def selector(iterator, d):
        for x in iterator:
            if x % d != 0:
                yield x
    picker = itertools.count(2)
    while True:
        # take the next available number
        prime = next(picker)
        yield prime
        # filter from the generator its multiples
        picker = selector(picker, prime)

尝试 list(itertools.islice(sieve(), 10)) 显示:

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

但我真的需要指出,这是一个纯粹的教育解决方案,用于展示事物的工作原理。我不建议将此解决方案用于任何生产代码。它在内部建立了大量的生成器,只有当你放下父生成器的句柄时,这些生成器才会被释放。这可能是一种资源浪费,您可以创建无限数量的素数,而无需创建无限数量的生成器。