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函數,是能夠傳遞我要讀取的大小的,而且偏移量會被記錄。
相關文章
相關標籤/搜索