filter函数与无限生成器结合使用遇到的问题

  python3中用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用list()来转换。 

       廖雪峰关于filter的使用 很好的解释了filter的作用,以及和生成器的结合,但是让人疑惑的是:

it = filter(_not_divisible(n), it) # 构造新序列

  以上这段代码的具体实现细节是什么?it是个生成器,使用filter对其进行操作的时候,并没有陷入无限循环,难道是对当前已生成的所有数据进行操作?那为什么在9这个数的时候,又能被已经跳过的3给滤除?有可能是filter给原有的it添加了判断规则,在生成9这个数的时候,自动执行filter规则?带着这个疑问,在官网的文章中并没有找到合理的解释。但是发现如下代码可以使用同样的功能,给了更多的思考线索。

from itertools import filterfalse
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n
        
def _not_divisible(n):
    return lambda x: x % n == 0

def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filterfalse(_not_divisible(n), it) # 构造新序列

# 打印1000以内的素数:
for n in primes():
    if n < 1000:
        print(n)
    else:
        break

  上述代码依旧能生成所有的素数,而关于filterfalse的源码如下所示:

def filterfalse(predicate, iterable):
    # filterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8
    if predicate is None:
        predicate = bool
    for x in iterable:
        if not predicate(x):
            yield x

  这段代码依旧让人觉得费解,为什么filterfalse没有返回值,it感觉确实被赋了值,这个值就行是什么?又做了如下实验:

a = [1, 2, 3, 4, 5]
a = iter(a)
b = filterfalse(lambda x: x%2==0, a)
print(type(a))
print(type(b))
print(next(b))
print(next(b))
print(next(b))

输出:
<class 'list_iterator'>
<class 'itertools.filterfalse'>
1
3
5

  从中可以看出it是指向了itertools.filterfalse这个类的一个实例,这个类是可迭代的,通过next(b)输出了结果。b是生成器,a也是迭代器,在b的生成器函数里使用迭代器a。

  所以,原始代码中filter重新赋值后的it是新的可迭代的类型,不是原始的生成器。就算是type(_odd_iter)也是<class 'function'>,具有生成器功能的函数。

结论

  在primes循环while中,每次it会重新赋予一个新的生成器,这个生成器嵌套了之前的生成器,是递归调用,最终的效果正如之前的猜想,相当于添加了规则,每次next的时候,对所有的规则遍历一遍。具体的代码执行是,每一个生成器函数都保存在内存中,一个生成器函数又嵌套了上一个生成器函数,每次next的时候,都会把之前所有的生成器函数递归一遍,等同于执行所有的规则。每执行一次训练就会加一层嵌套,所有每次next的遍历深度都在增加。

  最后发现自己好蠢,it = filterfalse(_not_divisible(n), it) 这行代码就是明显的递归调用。

代码执行过程

输出:2

①it

输出:3

②it -> ①it

n=next(it) -> for x in ①iterable(相当于在原有生成器上执行了一次next) -> x=5,(判断能被3整除吗)yield 5  

n=5

输出:5

③it -> ②it -> ①it

n=next(it) -> for x in ②iterable -> for x in ①iterable -> x=7,(判断能被3整除吗)yield 7 -> ②iterable -> x=7,(判断能被5整除吗)yield 7

n=7

输出:7

④ -> ③it -> ②it -> ①it

n=next(it) -> for x in ③iterable  -> for x in ②iterable -> for x in ①iterable -> x=9 -> 能被3除去(没有进入if,没有执行yield,继续for循环) -> x=11, (判断能被3整除吗)yield 11 -> ②iterable -> x=11, (判断能被5除吗)yield 11 -> ③iteable -> x=11, (判断能被7整除吗)yield 11 

n=11

输出:11

总结

  在递归调用中,yield相当于return,for x in iterable相当于调用嵌套函数。每次产生新的数据都是从最原始_odd_iter()产生,每返回一层,就进行一次if规则判断,通过判断就不断向上层返回,不通过就再次向最底层遍历。

posted @ 2018-12-04 17:06  mjl_cv  阅读(594)  评论(1编辑  收藏  举报