Python之美[從菜鳥到高手]--生成器之全景分析

發表於2016-04-26

 yield指令,可以暫停一個函式並返回中間結果。使用該指令的函式將儲存執行環境,並且在必要時恢復。

生成器比迭代器更加強大也更加複雜,需要花點功夫好好理解貫通。

看下面一段程式碼:

只要函式中包含yield關鍵字,該函式呼叫就是生成器物件。

我們可以看到,gen()並不是函式呼叫,而是產生生成器物件。

生成器物件支援幾個方法,如gen.next() ,gen.send() ,gen.throw()等。

呼叫生成器的next方法,將執行到yield位置,此時暫停執行環境,並返回yield後的值。所以列印出的是0,暫停執行環境。

再呼叫next方法,你也許會好奇,為啥列印出兩個值,不急,且聽我慢慢道來。

上一次呼叫next,執行到yield 0暫停,再次執行恢復環境,給tmp賦值(注意:這裡的tmp的值並不是x的值,而是通過send方法接受的值),由於我們沒有呼叫send方法,所以

tmp的值為None,此時輸出None,並執行到下一次yield x,所以又輸出1.

到了這裡,next方法我們都懂了,下面看看send方法。

上一次執行到yield 1後暫停,此時我們send(‘hello’),那麼程式將收到‘hello’,並給tmp賦值為’hello’,此時tmp==’hello’為真,所以輸出’world’,並執行到下一次yield 2,所以又列印出2.(next()等價於send(None))

當迴圈結束,將丟擲StopIteration停止生成器。

看下面程式碼:

正如你所預料的,列印出’nono’,由於沒有額外的yield,所以將直接丟擲StopIteration。

看下面程式碼,理解throw方法,throw主要是向生成器傳送異常。

呼叫gg.next很明顯此時輸出‘something’,並在yield ‘something’暫停,此時向gg傳送ValueError異常,恢復執行環境,except  將會捕捉,並輸出資訊。

理解了這些,我們就可以向協同程式發起攻擊了,所謂協同程式也就是是可以掛起,恢復,有多個進入點。其實說白了,也就是說多個函式可以同時進行,可以相互之間傳送訊息等。

這裡有必要說一下multitask模組(不是標準庫中的),看一段multitask使用的簡單程式碼:

結果:

如果不是使用生成器,那麼要實現上面現象,即函式交錯輸出,那麼只能使用執行緒了,所以生成器給我們提供了更廣闊的前景。

如果僅僅是實現上面的效果,其實很簡單,我們可以自己寫一個。主要思路就是將生成器物件放入佇列,執行send(None)後,如果沒有丟擲StopIteration,將該生成器物件再加入佇列。

當然,multitask實現的肯定不止這個功能,有興趣的童鞋可以看下原始碼,還是比較簡單易懂的。

 

#增補 2014/5/21

之前我在南京面試Python時遇到這麼一道題目:

如果上面class Task看懂了,那麼這題很簡單,其實就是考你用yield模擬執行緒排程,解決如下:

相關文章