content:
1. 什麼是生成器
2. 生成器的實現
3. 生成器的應用
一.生成器簡介
1.什麼是生成器
在 Python 中,使用了 yield 的函數被稱爲生成器(generator)。
跟普通函數不一樣的是,生成器是一個返回迭代器的函數,只能用於迭代操做,更簡單點理解生成器就是一個迭代器。
在調用生成器運行的過程當中,每次遇到 yield 時函數會暫停並保存當前全部的運行信息,返回 yield 的值, 並在下一次執行 next() 方法時從當前位置繼續運行。
調用一個生成器函數,返回的是一個迭代器對象。
能夠看到,普通函數就是返回的return,而生成器函數是生成了一個生成器對象。
爲何能和普通函數不同返回生成器對象?
由於在python在運行以前進行編譯成字節碼。發現了yield關鍵字,因此在編譯的時候就定義了。
生成器對象,實際上也是實現了咱們的迭代協議的。
爲啥會用到生成器?
簡單舉個例子:
列表全部數據都在內存中,若是有海量數據的話將會很是耗內存。若是僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
若是列表元素能按照某種算法推算出來,那咱們就能夠在循環的過程當中不斷推算出後續的元素,這樣就沒必要建立完整的list,從而節省大量的空間。
生成器在python中的設計使用
實現了延遲求值和惰性求值,也是後面協程實現的基礎。
2.生成器怎麼用
例子:實現斐波拉契數列
#input
def fib(x):
if x<3:
return 1
else:
return fib(x-1)+fib(x-2)
def fib2(x):
n=0
last=1
sum=0
while n<x:
yiled last
sum,last=last,sum+last
n=n+1
if __name__=="__main__":
f=fib(6)
print(f)
f2=fib2(6)
for i in f2:
print(i)
pass
#output
8
1
1
2
3
5
8
二. 生成器的實現
生成器其實用起來仍是比較簡單的,可是不理解原理的時候,用的時候是否是虛虛的。
1.python函數的工做原理
python解釋器其實是用c來寫的。解釋器會用C實現的函數(
PyEval_EvalFramEx)去執行函數。
這個 PyEval_EvalFramEx 首先會建立一個棧幀(Stack Frame)對象,就是那種記錄上下文的堆棧。注意python裏一切皆對象哦。
而後會將代碼也變成字節碼對象。查看一個函數的字節碼:
#input
def foo():
bar
def bar():
pass
import dis
print(dis.dis(foo))
#output:
2 0 LOAD_GLOBAL 0 (bar)
2 POP_TOP
4 LOAD_CONST 0 (None)
6 RETURN_VALUE
None
在調用函數以前,會建立那個棧幀對象,而後在上下文中,運行這個全局惟一的字節碼。
當foo調用bar的時候,又會建立一個棧幀,而後將bar的控制權交給foo的棧幀對象。
全部棧幀都是分配在堆內存(不去釋放,就一直在內存中)上,這就決定了棧幀能夠獨立於調用者存在。
什麼意思呢?
就是在foo函數退出以後,咱們仍然能夠找到以前調用過的foo,或者它的子函數bar的棧幀,並無和靜態語言同樣函數運行完了以後就被釋放。
2.生成器對象原理
假設咱們實現一個生成器函數:
def gen_func():
yield 1
name="bobby"
yield 2
age=30
return "tangrong"
這個生成器對象實際上以下圖所示:
實際上,就是在1中的PyFrameObject上面,再封了一層,爲PyGenObject。
而且,再yield時候,實際上就是暫停了最近的那句代碼。當時的上下文都是被保存的,即f_lasti,f_locals。在任何地方均可以暫停和控制它。
查看yield時,保存的lasti和locals:
#input
def gen_func():
yield 1
name="bobby"
yield 2
age=30
return "tangrong"
gen=gen_func()
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
#outpu:
-1
{}
2
{}
12
{'name': 'bobby'}
三.生成器的應用
1.生成器在Userlist中的應用
咱們知道,對list能夠進行循環遍歷。由於其是可迭代的。若是實現了__getitem__也是能夠進行for遍歷的。並且是會先去查找__iter__,沒有發現__iter__魔法方法纔會去找__getitem__方法。
咱們去看list類的源碼的時候,其實就是提供了給咱們看的接口,實際的c語言實現並看不到。並且咱們在定製本身的List類的時候,是徹底不提倡去繼承list的,由於裏面的不少關鍵方法是不能被重寫的。可是python提供了UserList,即python實現的list。
首先,UserList是繼承的MutableSequence。
而在MutableSequence的__iter__的實現中,就應用到了生成器。
2.生成器是如何讀取大文件的(如何使用生成器表達式)
#將一個500g的文件讀取出來 寫入數據庫 而且這個文件只有一行數據,有特定的分隔符。
#若是是多行的話,用open一行行讀取仍是能夠的。
#可是實際上!! 文件對象的read函數,是能夠傳遞我要讀取的大小的,而且偏移量會被記錄。