<code>yield</code>的功能類似于<code>return</code>,但是不同之處在于它傳回的是<code>生成器</code>。
生成器是通過一個或多個<code>yield</code>表達式構成的函數,每一個生成器都是一個疊代器(但是疊代器不一定是生成器)。
如果一個函數包含<code>yield</code>關鍵字,這個函數就會變為一個生成器。
生成器并不會一次傳回所有結果,而是每次遇到<code>yield</code>關鍵字後傳回相應結果,并保留函數目前的運作狀态,等待下一次的調用。
由于生成器也是一個疊代器,那麼它就應該支援<code>next</code>方法來擷取下一個值。
除了<code>next</code>函數,生成器還支援<code>send</code>函數。該函數可以向生成器傳遞參數。
最經典的例子,生成無限序列。
正常的解決方法是,生成一個滿足要求的很大的清單,這個清單需要儲存在記憶體中,很明顯記憶體限制了這個問題。
如果使用生成器就不需要傳回整個清單,每次都隻是傳回一個資料,避免了記憶體的限制問題。
生成器的源碼在<code>objects/genobject.c</code>。
在解釋生成器之前,需要講解一下python虛拟機的調用原理。
python虛拟機有一個棧幀的調用棧,其中棧幀的是<code>pyframeobject</code>,位于<code>include/frameobject.h</code>。
棧幀儲存了給出代碼的的資訊和上下文,其中包含最後執行的指令,全局和局部命名空間,異常狀态等資訊。<code>f_valueblock</code>儲存了資料,<code>b_blockstack</code>儲存了異常和循環控制方法。
舉一個例子來說明,
那麼,相應的調用棧如下,一個py檔案,一個類,一個函數都是一個代碼塊,對應者一個frame,儲存着上下文環境以及位元組碼指令。
每一個棧幀都擁有自己的資料棧和block棧,獨立的資料棧和block棧使得解釋器可以中斷和恢複棧幀(生成器正式利用這點)。
python代碼首先被編譯為位元組碼,再由python虛拟機來執行。一般來說,一條python語句對應着多條位元組碼(由于每條位元組碼對應着一條c語句,而不是一個機器指令,是以不能按照位元組碼的數量來判斷代碼性能)。
調用<code>dis</code>子產品可以分析位元組碼,
其中,
由了上面對于調用棧的了解,就可以很容易的明白生成器的具體實作。
生成器的源碼位于<code>object/genobject.c</code>。
<code>next</code>與<code>send</code>函數,如下
從上面的代碼中可以看到,<code>send</code>和<code>next</code>都是調用的同一函數<code>gen_send_ex</code>,差別在于是否帶有參數。
<code>pyeval_evalframeex</code>函數的功能為執行位元組碼并傳回結果。
舉一個例子,<code>f_back</code>上一個frame,<code>f_lasti</code>上一次執行的指令的偏移量,
結果如下,其中第三行的英文為操作碼,對應着上面的<code>opcode</code>,每次switch都是在不同的<code>opcode</code>之間進行選擇。
![]()
Python yield與實作