Discuss / Python / 使用 list 和 Iterator 来做 filter, 为了正确运行所需接受的 命题 有微妙的不同

使用 list 和 Iterator 来做 filter, 为了正确运行所需接受的 命题 有微妙的不同

Topic source

我这里把返回 bool 值的函数都叫做命题

def gen(size):
    # 生成模 6 余 1, 5 的序列
    for n in range(6, size, 6):
        yield n - 1
        yield n + 1


def prime(size):
    yield 2
    n = 3
    yield 3
    sieve = list(gen(size))
    while n * n < size:
        n = sieve[0]
        yield n
        sieve = list(filter(lambda x: x % n, sieve))
    for n in sieve:
        yield n


def prime_iter(size):
    yield 2
    n = 3
    yield 3
    sieve = gen(size)
    while n * n < size:
        n = next(sieve)
        yield n
        sieve = filter((lambda n: lambda x: x % n)(n), sieve)
        # sieve = filter(lambda x: x % n, sieve) 出错! 
    for n in sieve:
        yield n


print(*prime(122))
print(*prime_iter(122))

很疑惑,疑惑在wrong answer,错因不明(主要是因为迭代器难以调试),错误结果如下第二行所示:

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113

2 3 5 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49 53 55 59 61 67 71 73 77 79 83 85 89 95 97 101 103 107 109 113 115 119 121  

和正文程序比对发现:为了让使用迭代器的筛法程序正确运行,需要设一个传参函数,把循环变量 n 用实际参数保存,这样才能得到正确的结果。

提到循环变量、传参函数,那就要复习“返回函数”那节(我本章到现在的阅读顺序是 map - 匿名函数 - 偏函数 - 返回函数 - 装饰器 - reduce - filter)。那里说:

如果甲函数返回乙函数,那么乙函数保存了甲函数的局部变量的地址,以供(除赋值之外的)引用。但是如果甲函数通过循环结构产生一族乙函数,乙函数直接调用甲函数的循环变量,那么只能调用到循环变量的终值,因为返回乙函数时并不是真正复制甲函数的局部变量及其此刻的取值,而返回函数之后这个局部变量被甲函数篡改了。乙函数内必须嵌套另一个函数,用它建立实际参数的过程复制**甲函数的局部变量及其此刻的取值

这就是 python 闭包的特性。

回到我们的程序。sieve = filter(lambda x: x % n, sieve) 出错,而 sieve = filter((lambda n: lambda x: x % n)(n), sieve) 就可以改正,这说明迭代器也是这样的闭包:当它调用(比如通过next(sieve)等方式)时,才会临时计算其值。

sieve = filter(lambda x: x % n, sieve) 这句迭代中,sieve 定义在前一个循环,使用在后一个循环(是互为前后,不是隔一个循环)。这其中,n 迭代到了新值,因而可以认为这是错误所在。

一言以蔽之:Iterator 是惰性计算的


  • 1

Reply