1. 程式人生 > >PythonI/O進階學習筆記_9.python的生成器

PythonI/O進階學習筆記_9.python的生成器

 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函式,是可以傳遞我要讀取的大小的,並且偏移量會被記錄。