yield指令,可以暫停一個函數並返回中間結果。使用該指令的函數將儲存執行環境,並且在必要時恢複。
產生器比迭代器更加強大也更加複雜,需要花點功夫好好理解貫通。
看下面一段代碼:
def gen(): for x in xrange(4): tmp = yield x if tmp == 'hello': print 'world' else: print str(tmp)
只要函數中包含yield關鍵字,該函數調用就是產生器對象。
g=gen()print g #<generator object gen at 0x02801760>print isinstance(g,types.GeneratorType) #True
我們可以看到,gen()並不是函數調用,而是產生產生器對象。
產生器對象支援幾個方法,如gen.next() ,gen.send() ,gen.throw()等。
print g.next() # 0
調用產生器的next方法,將運行到yield位置,此時暫停執行環境,並返回yield後的值。所以列印出的是1,暫停執行環境。
print g.next() #None 1
再調用next方法,你也許會好奇,為啥列印出兩個值,不急,且聽我慢慢道來。
上一次調用next,執行到yield 0暫停,再次執行恢複環境,給tmp賦值(注意:這裡的tmp的值並不是x的值,而是通過send方法接受的值),由於我們沒有調用send方法,所以
tmp的值為None,此時輸出None,並執行到下一次yield x,所以又輸出1.
到了這裡,next方法我們都懂了,下面看看send方法。
print g.send('hello') #world 2
上一次執行到yield 1後暫停,此時我們send('hello'),那麼程式將收到‘hello',並給tmp賦值為’hello',此時tmp=='hello'為真,所以輸出'world',並執行到下一次yield 2,所以又列印出2.(next()等價於send(None))
當迴圈結束,將拋出StopIteration停止產生器。
看下面代碼:
def stop_immediately(name): if name == 'skycrab': yield 'okok' else: print 'nono's=stop_immediately('sky')s.next()
正如你所預料的,列印出’nono',由於沒有額外的yield,所以將直接拋出StopIteration。
nonoTraceback (most recent call last): File "F:\python workspace\Pytest\src\cs.py", line 170, in <module> s.next()StopIteration
看下面代碼,理解throw方法,throw主要是向產生器發送異常。
def mygen(): try: yield 'something' except ValueError: yield 'value error' finally: print 'clean' #一定會被執行gg=mygen()print gg.next() #somethingprint gg.throw(ValueError) #value error clean
調用gg.next很明顯此時輸出‘something’,並在yield ‘something’暫停,此時向gg發送ValueError異常,恢複執行環境,except 將會捕捉,並輸出資訊。
理解了這些,我們就可以向協同程式發起攻擊了,所謂協同程式也就是是可以掛起,恢複,有多個進入點。其實說白了,也就是說多個函數可以同時進行,可以相互之間發送訊息等。
這裡有必要說一下multitask模組(不是標準庫中的),看一段multitask使用的簡單代碼:
def tt(): for x in xrange(4): print 'tt'+str(x) yielddef gg(): for x in xrange(4): print 'xx'+str(x) yieldt=multitask.TaskManager()t.add(tt())t.add(gg())t.run()
結果:
tt0xx0tt1xx1tt2xx2tt3xx3
如果不是使用產生器,那麼要實現上面現象,即函數交錯輸出,那麼只能使用線程了,所以產生器給我們提供了更廣闊的前景。
如果僅僅是實現上面的效果,其實很簡單,我們可以自己寫一個。主要思路就是將產生器對象放入隊列,執行send(None)後,如果沒有拋出StopIteration,將該產生器對象再排入佇列。
class Task(): def __init__(self): self._queue = Queue.Queue() def add(self,gen): self._queue.put(gen) def run(self): while not self._queue.empty(): for i in xrange(self._queue.qsize()): try: gen= self._queue.get() gen.send(None) except StopIteration: pass else: self._queue.put(gen)t=Task()t.add(tt())t.add(gg())t.run()
當然,multitask實現的肯定不止這個功能,有興趣的童鞋可以看下源碼,還是比較簡單易懂的。