关于用埃氏筛选法求素数python代码的一些理解
源代码
来自廖雪峰-filter
算法描述参考原文。
代码块如下:
def _odd_iter(): # 生成一个无限序列的奇数Generator
z = 1
while True:
z = z+2
yield z
def _not_divisible(y): # 用埃氏筛选法过滤掉奇数序列中的合数
return lambda x : x % y > 0
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) # 构造新序列
for n in primes(): # 打印前1000个数中的素数
if n < 1000:
print(n)
else:
break
疑问
将语句it = filter(_not_divisible(n), it) 修改成it = filter(lambda x : x % n >0, it)这句,表面看起来似乎等价,执行结果却完全不同。
很明显,当语句为it = filter(lambda x : x % y >0, it),执行的结果是不对的。这涉及到闭包。“闭包本质上是一个函数,它有两部分组成,一个函数和一个变量,闭包使得这些变量的值始终保存在内存中。”听不懂对不对?没关系,我也理解的似是而非,以后使用场景多了理解更深了再来更新。具体到这个例子,使用it = filter(lambda x : x % n>0, it)这条语句,当运行到奇数9的时候,x=9,而n一直传入的是7,自然达不到过滤的目的。
事实上,等价写法应该是 it = _filter(lambda x,y=n: x % y >0, it)
那么当使用语句it = filter(_not_divisible(n), it) 的时候,例如当运行到奇数7的时候,为什么y的值会是3和5呢?
看了这个答案后(lambda闭包-闭包到底捕获了哪个参数?),我理解为这里的y值为3的这个参数不是后来传入进去的,而是上一次已经把_not_divisible(3)存成一个闭包函数了,以后同理,会存
_not_divisible(5),_not_divisible(7)……这些闭包函数。
为了验证这个想法,我们把程序改下一下:
arrlist = [] # 声明一个全局list,用来存储闭包函数
def _odd_iter():
z = 1
while True:
z = z+2
yield z
def _not_divisible(y):
def _f(x):
return x % y > 0
return _f
#return lambda x : x % y > 0
def _filter(func,seq):
# filter_list = []
for s in seq:
if func(s):
if( not(func in arrlist)):
arrlist.append(func) # 追加程序运行时产生的新的闭包函数
# filter_list.append(s)
yield s
def primes():
yield 2
it = _odd_iter() # 初始序列,it是一个奇数序列
while True:
n = next(it) # 返回序列的第一个数
yield n
# it = filter(_not_divisible(n), it) # 构造新序列
_test = _not_divisible(n) # 并没有传入x的值,所以return x % y 并没有执行,直接将_f返回给_test
it = _filter(_test, it)
for m in primes():
if m < 1000:
print(m)
else:
break
这里要注意:
filter这个高级函数某种程度上和_filter函数是等价的,参考这里filter内部实现原理
def _filter(func,seq):
# filter_list = []
for s in seq:
if func(s):
if( not(func in arrlist)):
arrlist.append(func) # 追加程序运行时产生的新的闭包函数
# filter_list.append(s)
yield s
单步调试新的代码:
执行到奇数7
此时arrlist里的有两个元素,并且执行两个不同地址的_not_divisible.闭包函数。
让我们来看看这里面是什么:
虽然看不到arrlist里面的具体的内容, 但是根据测试结果可以推断arrlist[0]应该是_not_divisible(3),arrlist[1]应该是_not_divisible(5)。
至于为什么会遍历这些闭包函数,我个人的理解是因为 for s in seq 这条语句的关系,同时又因为yield s,s 一直取值为seq序列中的最新值。实际调试的时候也发现,会在这里反复执行,调用不同的闭包函数。更深层的东西就不了解了。