關於用埃氏篩選法求素數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呢?
看了這個答案後(
_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序列中的最新值。實際除錯的時候也發現,會在這裡反覆執行,呼叫不同的閉包函式。更深層的東西就不瞭解了。