Python: generator和filter在filter()生成prime list的代码中是如何工作的

Python: how does the generator and filter work in the codes generating prime list with filter()

注意: 这个问题与 不同,尽管它们都与 Python 代码有关,代码查找不超过给定限制的所有素数。

核心代码其实很简单,但是我很难理解它是如何工作的。这就是我添加一些调试打印的原因。

def _odd_number_generator():
    x = 1
    while True:
        x += 2
        print('    _odd_number_generator, x=', x)
        yield x

def _not_divisible(n):
    def func(x):
        print("      filter calling on x:", x, ", n:", n)
        return x % n > 0
    return func


def _primes():
    yield 2                                 # return first prime: 2
    odd_numbers = _odd_number_generator()   
    print("  in _primes, #a:  odd_numbers=", odd_numbers)
    while True:
        print("  in _primes, #b:         before next(filter) odd_numbers=", odd_numbers)

        # I know this line calling _odd_number_generator and _not_divisible, 
        # but how it works
        n = next(odd_numbers)   

        print("  in _primes, #c:         begin yield n:", n)
        yield n
        print("  in _primes, #d: n=", n, ", after yield  odd_numbers=", odd_numbers)
        odd_numbers = filter(_not_divisible(n), odd_numbers)    
        print("  in _primes, #e: n=", n, ", after filter odd_numbers=", odd_numbers)


def print_prime_numbes():
    for n in _primes():                                          
        print("  in print_prime_numbes, n = ", n)
        if n < 30:
            print(n)
            print()
            print("print_prime_numbes, begin next loop: n=", n)
        else:
            break

def test_print_prime_numbes():
    print("test_output_triangles() >>>")
    print_prime_numbes()

中的答案对理解链式迭代器很有帮助。但是,我仍然无法理解 _odd_number_generator 和 _not_divisible 正在处理号码 25 时被调用。

比如下面是运行ning时的一段输出:

print_prime_numbes, begin next loop: n= 23
  in _primes, #d: n= 23 , after yield  odd_numbers= <filter object at 0x000002B0E02366D8>
  in _primes, #e: n= 23 , after filter odd_numbers= <filter object at 0x000002B0E0236F98>
  in _primes, #b:         before next(filter) odd_numbers= <filter object at 0x000002B0E0236F98>
    _odd_number_generator, x= 25
      filter calling on x: 25 , n: 3
      filter calling on x: 25 , n: 5
    _odd_number_generator, x= 27
      filter calling on x: 27 , n: 3
    _odd_number_generator, x= 29
      filter calling on x: 29 , n: 3
      filter calling on x: 29 , n: 5
      filter calling on x: 29 , n: 7
      filter calling on x: 29 , n: 11
      filter calling on x: 29 , n: 13
      filter calling on x: 29 , n: 17
      filter calling on x: 29 , n: 19
      filter calling on x: 29 , n: 23
  in _primes, #c:         begin yield n: 29
  in print_prime_numbes, n =  29
29

在这里,因为 25 是可整除的,所以正在生成下一个数字 27。我想知道是什么让调用生成 27?

[已编辑]

在 yield 23 之后,进入下一个循环,odd_numbers 应该是这样的: 过滤器(_not_divisible(23),过滤器(_not_divisible(19)...过滤器(_not_divisible(7),过滤器(_not_divisible(5),过滤器(_not_divisible(5),过滤器(_not_divisible(3), _odd_generator())).

当运行宁"yield n"时,正在生成下一个数字25并检查可整除性,而_not_divisible return False。在这里,看起来 'yield' 将 运行 下一个 _odd_generator() 并检查新数字是否可以除以 3,4,5,..23 直到它得到素数。但是我想详细了解这里的机制。

为了更好的理解,我们也可以把filter看成一个生成器:

def filter(condition, iterable):
    for value in iterable:      # 1. consume values from `iterable`
        if condition(value):    # 2. test `condition` for `value`
            yield value         # 3. yield any valid `value`

换句话说,odd_numbers = filter(_not_divisible(n), odd_numbers) 是一个生成器 (filter) 包裹了另一个生成器 (_odd_number_generator)。对于每个素数,一个新的 filter 环绕现有的环绕过滤器。查看其中一个初始案例,我们有以下设置:

odd_numbers = filter(_not_divisible(n=7),  # <filter A>
    filter(_not_divisible(n=5),            # <filter B>
        filter(_not_divisible(n=3),        # <filter C>
            _odd_number_generator()        # <odd_numbers @ x=7>
))

现在,如果我们调用 next(odd_numbers) 会发生什么?

  • <filter A>:1 通过调用 next(<filter B>) 获取 value
    • <filter B>:1 通过调用 next(<filter C>) 获取 value
      • <filter C>:1 通过调用 next(<odd_numbers @ x=7>) 获取 value
        • <odd_numbers @ x=7>x+=2 增加到 x=9 并产生它
      • <filter C>:2测试_not_divisible(n=3)(9)发现无效
      • <filter C>:3 跳过 并且循环继续
      • <filter C>:1 通过调用 next(<odd_numbers @ x=9>) 获取 value
        • <odd_numbers @ x=9>x+=2 增加到 x=11 并产生它
      • <filter C>:2 测试 _not_divisible(n=3)(11) 并发现有效
      • <filter C>:3 产生 11
    • <filter B>:2 测试 _not_divisible(n=5)(11) 并发现有效
    • <filter B>:3 产生 11
  • <filter A>:2 测试 _not_divisible(n=7)(11) 并发现有效
  • <filter A>:3 产生 11

重要的是 _not_divisible(n=3) 不让值 9 通过。相反,<filter C> 中的循环获取另一个值 而不会 屈服于 <filter B><filter A>.

随着越来越多的 filter(_not_divibible(n), ...) 层包裹在 _odd_number_generator() 周围,还有其他层可以完成 "skip yield and request new value"。中间生成器在产生之前可以消耗 几个 值的一般原则保持不变。