Python yield與實現

發表於2016-08-01

Python yield與實現

yield的功能類似於return,但是不同之處在於它返回的是生成器

生成器

生成器是通過一個或多個yield表示式構成的函式,每一個生成器都是一個迭代器(但是迭代器不一定是生成器)。

如果一個函式包含yield關鍵字,這個函式就會變為一個生成器。

生成器並不會一次返回所有結果,而是每次遇到yield關鍵字後返回相應結果,並保留函式當前的執行狀態,等待下一次的呼叫。

由於生成器也是一個迭代器,那麼它就應該支援next方法來獲取下一個值。

基本操作

除了next函式,生成器還支援send函式。該函式可以向生成器傳遞引數。

應用

最經典的例子,生成無限序列。

常規的解決方法是,生成一個滿足要求的很大的列表,這個列表需要儲存在記憶體中,很明顯記憶體限制了這個問題。

如果使用生成器就不需要返回整個列表,每次都只是返回一個資料,避免了記憶體的限制問題。

生成器原始碼分析

生成器的原始碼在Objects/genobject.c

呼叫棧

在解釋生成器之前,需要講解一下Python虛擬機器的呼叫原理。

Python虛擬機器有一個棧幀的呼叫棧,其中棧幀的是PyFrameObject,位於Include/frameobject.h

棧幀儲存了給出程式碼的的資訊和上下文,其中包含最後執行的指令,全域性和區域性名稱空間,異常狀態等資訊。f_valueblock儲存了資料,b_blockstack儲存了異常和迴圈控制方法。

舉一個例子來說明,

那麼,相應的呼叫棧如下,一個py檔案,一個類,一個函式都是一個程式碼塊,對應者一個Frame,儲存著上下文環境以及位元組碼指令。

每一個棧幀都擁有自己的資料棧和block棧,獨立的資料棧和block棧使得直譯器可以中斷和恢復棧幀(生成器正式利用這點)。

Python程式碼首先被編譯為位元組碼,再由Python虛擬機器來執行。一般來說,一條Python語句對應著多條位元組碼(由於每條位元組碼對應著一條C語句,而不是一個機器指令,所以不能按照位元組碼的數量來判斷程式碼效能)。

呼叫dis模組可以分析位元組碼,

其中,

生成器原始碼分析

由了上面對於呼叫棧的理解,就可以很容易的明白生成器的具體實現。

生成器的原始碼位於object/genobject.c

生成器的建立

send與next

nextsend函式,如下

從上面的程式碼中可以看到,sendnext都是呼叫的同一函式gen_send_ex,區別在於是否帶有引數。

位元組碼的執行

PyEval_EvalFrameEx函式的功能為執行位元組碼並返回結果。


舉一個例子,f_back上一個Frame,f_lasti上一次執行的指令的偏移量,

結果如下,其中第三行的英文為操作碼,對應著上面的opcode,每次switch都是在不同的opcode之間進行選擇。

相關文章