老闆下達了一個任務,說是要每天遍歷一百萬資料,找到日誌中重要地段的資訊,重新記錄存放?到底要怎麼搞呢?
小張回答說列表生成式不就可以嘛,老闆笑而不語。小王答曰用生成器吧。此時,老闆開心地點了點頭。
小張不明白,列表就可以很好處理的問題,為什麼要用生成器呢?
後來,小張明白了......
生成器 generator
我們知道,透過列表生成式,我們可以直接建立一個列表。但是,受客觀條件影響,我們使用的機器都是受到記憶體限制,所以列表容量肯定是有限的。這樣生成一個列表不僅佔用記憶體,如果我們只需要訪問一部分值,還會造成大量的記憶體浪費。
在Python2中可以做一個嘗試,感興趣的不妨試試。
開啟IDE互動介面,進入Python互動視窗,range(1000000)試試,不行可以range(10000000)再試試,那時候就可以很明顯的感受到記憶體計算的差別了。
正是因為這個缺點,在python3中對range進行了最佳化,range(10000)得到的結果是range(0,10000),這樣的一個生成式了。
所以,如果列表元素可以按照某種演算法推算出來,那我們是否可以在迴圈的過程中不斷推算出後續的元素呢?
像上面這個迴圈,每次迴圈只是+1而已,我們完全可以寫一個演算法,讓他執行一次就自動1,這樣就不必建立完整的,從而節省大量的空間。
在 Python中,這種一邊迴圈一邊計算後面元素的機制,稱為生成器: generator
生成器的生成
要建立一個 gonerator,有很多種方法。
第一種方法很簡,只要把一個列表生成式的改成()的形式,就建立了個gonerator
>>> [ x for x in range(10)][0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>>> ( x for x in range(10))<generator object <genexpr> at 0x000002C103210E40>>>> g = ( x for x in range(10))
如上面的程式碼所示,我們可以直接得出列表的所有元素。那麼,生成器要怎麼訪問呢?
生成器的元素獲取next()
生成器的元素,如果要一個一個打印出來,可以透過next()函,獲得generator的下一個返回值。
>>> g = ( x for x in range(10))>>> next(g)0>>> next(g)1>>> next(g)2>>> next(g)3>>> next(g)4>>> next(g)5>>> next(g)6>>> next(g)7>>> next(g)8>>> next(g)9>>> next(g)Traceback (most recent call last): File "<pyshell#13>", line 1, in <module> next(g)StopIteration
此時,我們每一次呼叫next就會生成一個新的元素,直到最後的時候會丟擲一個異常,而這個異常,我們可以通過後面的方法規避掉。
for迴圈
當然,上面這種不斷呼叫next(g)實在是太變態了。正確的方法是使用tor迴圈,因為 gonorator也是可選代(遍歷)物件。
g = ( x for x in range(10))for i in g: print(i)0123456789
透過for迴圈來迭代它,就不需要關心會不會拋異常了。
生成器函式
特點:動態獲取資料,迴圈一次,計算一次,執行一次
生成器函式:具有yield語句。
案例:生成器的思想解決斐波那契數列
原始的方法
a = 0b = 1count = 0while count < 20: temp = a a = b b = a + b count += 1 print("b:",b)
生成器函式的方法
def fib(n): a = 0 b = 1 count = 0 while count < n: temp = a a = b b = a + b count += 1 yield bfor i in fib(20): print("i:", i)
那為什麼要用生成器呢?顯然,用生成器在逼格上高,效能高效,為什麼不用呢?