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规则判断,通过判断就不断向上层返回,不通过就再次向最底层遍历。