为何使用lambda表达式的filter在质数generator中无法正常工作?
为什么带lambda的filter在质数生成器里会输出9?
哈哈,这个坑我当初写埃氏筛的时候也踩过!问题的核心是Python闭包的延迟绑定特性——lambda表达式里的变量不是在定义时就固定下来,而是在lambda被实际调用的时候才去查找变量的当前值。
先还原你的错误场景
假设你的质数生成器代码大概是这样的(这是新手写埃氏筛的典型错误):
import itertools def primes(): candidates = itertools.count(2) while True: prime = next(candidates) yield prime # 试图过滤掉当前prime的倍数,但这里的lambda有问题 candidates = filter(lambda x: x % prime != 0, candidates)
运行这个代码,你会发现前几个数是2、3、4、5、7、8、9...——4和9这些非质数都混进来了,这就是你遇到的问题。
问题到底出在哪?
咱们一步步拆解:
- 第一次循环:
prime=2,yield 2,然后我们把candidates替换成一个filter对象,这个filter的lambda逻辑是x%prime !=0。注意:此时lambda并没有把prime=2存下来,只是“记住”了要使用外部变量prime。 - 第二次循环:
prime=3,yield3,然后candidates又被替换成新的filter对象,lambda同样引用外部的prime变量。 - 现在当迭代到4的时候,第一个filter(原本应该过滤所有2的倍数)开始执行lambda判断,但此时
prime已经变成了3!所以判断条件变成了4%3 !=0,结果是True,4就被当成质数保留下来了。 - 同理,当迭代到9的时候,第二个filter的lambda此时引用的
prime已经变成了5或者7(取决于当前循环到哪),9%5 !=0是True,9%7 !=0也是True,所以9就被当成质数输出了。
简单说:所有lambda都共享同一个prime变量,它们用的都是这个变量的最新值,而不是定义lambda时的那个prime值。
怎么解决?
解决方法很简单:把prime的值绑定到lambda的参数上,用默认参数的方式让lambda在定义时就捕获当前的prime值,而不是引用外部变量。修改后的代码如下:
import itertools def primes(): candidates = itertools.count(2) while True: prime = next(candidates) yield prime # 用默认参数p=prime绑定当前的prime值 candidates = filter(lambda x, p=prime: x % p != 0, candidates)
这样每个lambda都会在定义时把当时的prime值存在自己的默认参数p里,后续迭代时就不会再受外部prime变量变化的影响,过滤逻辑就正常了,9这类非质数就不会被输出了。
内容的提问来源于stack exchange,提问作者He Kunxi
相关产品推荐
相关产品推荐

