Python 迭代器的意外行为

Python 迭代器的意外行为,python,python-3.x,functional-programming,iterator,sieve-of-eratosthenes,Python,Python 3.x,Functional Programming,Iterator,Sieve Of Eratosthenes,我试图用迭代器实现Eratosthenes的筛选(因为我想更多地使用python进行函数编程)。不幸的是,发生了一些意想不到的行为。您可以在此视频中看到: 这是我的密码: def sieve_primes(stop=10): L = (x for x in range(2, stop+1)) while True: prime = next(L) L = filter(lambda x: x % prime != 0 or x == prime,

我试图用迭代器实现Eratosthenes的筛选(因为我想更多地使用python进行函数编程)。不幸的是,发生了一些意想不到的行为。您可以在此视频中看到:

这是我的密码:

def sieve_primes(stop=10):
    L = (x for x in range(2, stop+1))
    while True:
        prime = next(L)
        L = filter(lambda x: x % prime != 0 or x == prime, L)
        #L, M = itertools.tee(L)
        #print(list(M))
        yield prime
当两个注释行未注释时,它会工作(生成一个具有所需素数的迭代器对象)。否则,它只对每个数字进行迭代

我期待着你的回答:)
谢谢

以下内容如何?通常使用生成器表达式比使用map/filter/reduce更好

#!/usr/bin/env python3


def sieve_primes(stop=100):
    primes = []
    for candidate in range(2, stop+1):
        if not any(candidate % prime == 0 for prime in primes):
            primes.append(candidate)
            yield candidate


for prime in sieve_primes():
    print(prime)

您正在lambda中使用变量
prime
,该变量是从封闭范围继承的引用。当您的代码计算lambda时,它将在继承引用的范围内使用绑定到该引用的任何值。当您不使用
tee
并计算列表时,所有lambda函数都是相同的,并且对
prime
使用相同的值

tee
的工作原理是将结果存储在一个列表中,并在稍后再次询问时从该列表中提供给您,因此对于
prime
的每个值,它实际上会将过滤器应用于
L
中的所有值

您可以通过在
lambda
范围内绑定
prime
来修复此问题,方法是将其作为带有默认值的参数传递。这会将该值保存为函数对象的一部分,引用
prime
则是对该存储值的本地引用

def sieve_primes(stop=10):
    L = (x for x in range(2, stop+1))
    while True:
        prime = next(L)
        L = filter(lambda x: x % prime != 0 or x == prime, L)
        yield prime
下面一次又一次地给出了代码中发生的具体情况。为了方便起见,我在第一次迭代中将L表示为L1,在第二次迭代中将L表示为L2,依此类推

  • 在第一次迭代中
    prime=next(L)
    为2(如预期)。
    L1=过滤器(λx:x%prime!=0或x==prime,L)
    (延迟计算
    L
    的值,即仅根据需要计算值。
    产生prime
    将产生
    2
    预期值

  • 在第二次迭代中,
    prime=next(L1)
    。棘手的部分来了。
    L1
    filter对象,其值仅根据需要计算。因此,在第二次迭代中,
    prime=next(L1)时
    只执行一个从
    L
    计算的值。现在lambda使用prime作为
    2
    ,并计算一个
    3
    3%2!=0
    )的值,该值现在是
    prime
    L2=filter(lambda x:x%prime!=0或x==prime,L1)
    (L2的
    L2
    值是惰性计算的,即仅根据需要计算值。现在,您
    生成素数
    将生成
    3

  • 在第三次迭代中
    prime=next(L2)
    。现在事情变得有点复杂了。要从
    L2
    中得到一个值,你需要计算
    L1
    的一个值,要计算
    L1
    的一个值,你需要计算
    L
    的一个值。如果你没记错的话
    L
    现在将产生
    4
    ,这将被
    L1
    用来产生一个值。但是对
    prime
    的最新引用是
    3
    4%3!=0
    被计算为
    True
    。因此,
    L1
    产生
    4
    。因此,计算
    L2
    产生的值
    4%3!=0
    被计算为
    True
    所以
    prime=next(L2)
    4


在进一步的迭代中应用相同的逻辑,您将发现5,6,7,8,9…将在进一步的迭代中产生。

我怀疑您看到绑定在
prime
范围内的问题。请尝试
lambda x,prime=prime:
definition@PatrickHaugh太好了,它起作用了!你能解释一下那只虫子是什么吗?我真的不懂还有我所改变的。提前谢谢你!为什么你在True:prime=next(L)时做
而不仅仅是
用于L中的素数:
?为什么要将
范围
转换为生成器?顺便说一句,欢迎使用堆栈溢出!请查看@wjandrea,因为他们正在重新定义
L
每个循环,他们不能
用于L中的素数
。这将在原始
L
上创建一个不会更新的迭代器d以反映各种情况filters@ixam07看到这个类似的问题,它有一些更详细的答案,尽管他们没有说明为什么
tee
会改变你的输出:顺便说一句,你可以通过使用范围(2,停止+1,2)获得2倍的加速在创建时将2添加到素数列表中。好吧,我想我明白了!这实际上让我想起了“You don know JS”系列丛书中的一个例子,其中(var i=1;i@Patrick如果您对lambda的看法正确,它将使用绑定到该引用的任何值,但在这里不正确所有lambda函数都是相同的,并且对素数使用相同的值。问题不仅仅是lambda,而是lambda和过滤器的惰性求值的组合。@Ch3steR它以什么方式不正确?所有lambda函数都有相同的定义,对所有输入都会产生相同的结果,不是吗?@PatrickHaugh直到现在我都这么认为。我在不同的条件下对它进行了测试。你对lambda的用法的正确看法是,无论该引用绑定了什么值。
prime
实际上在每次迭代中都会发生变化。我正在发布详细的answer迭代分析。可能需要一些时间。@Ch3steR说清楚,我是说创建的多个lambda函数都是相同的,它们都会改变循环的每次迭代。因此,对于小于x的每个Pi(x)素数,我们没有唯一的lambda函数,而是Pi(x)用作过滤器的lambda函数都是相同的,尽管
prime
本身在每次迭代中都会发生变化。