yield指令,可以暫停一個函式並返回中間結果。使用該指令的函式將儲存執行環境,並且在必要時恢復。
生成器比迭代器更加強大也更加複雜,需要花點功夫好好理解貫通。
看下面一段程式碼:
1 2 3 4 5 6 7 |
def gen(): for x in xrange(4): tmp = yield x if tmp == 'hello': print 'world' else: print str(tmp) |
只要函式中包含yield關鍵字,該函式呼叫就是生成器物件。
1 2 3 |
g=gen() print g # print isinstance(g,types.GeneratorType) #True |
我們可以看到,gen()並不是函式呼叫,而是產生生成器物件。
生成器物件支援幾個方法,如gen.next() ,gen.send() ,gen.throw()等。
1 |
print g.next() # 0 |
呼叫生成器的next方法,將執行到yield位置,此時暫停執行環境,並返回yield後的值。所以列印出的是0,暫停執行環境。
1 |
print g.next() #None 1 |
再呼叫next方法,你也許會好奇,為啥列印出兩個值,不急,且聽我慢慢道來。
上一次呼叫next,執行到yield 0暫停,再次執行恢復環境,給tmp賦值(注意:這裡的tmp的值並不是x的值,而是通過send方法接受的值),由於我們沒有呼叫send方法,所以
tmp的值為None,此時輸出None,並執行到下一次yield x,所以又輸出1.
到了這裡,next方法我們都懂了,下面看看send方法。
1 |
print g.send('hello') #world 2 |
上一次執行到yield 1後暫停,此時我們send(‘hello’),那麼程式將收到‘hello’,並給tmp賦值為’hello’,此時tmp==’hello’為真,所以輸出’world’,並執行到下一次yield 2,所以又列印出2.(next()等價於send(None))
當迴圈結束,將丟擲StopIteration停止生成器。
看下面程式碼:
1 2 3 4 5 6 7 8 |
def stop_immediately(name): if name == 'skycrab': yield 'okok' else: print 'nono' s=stop_immediately('sky') s.next() |
正如你所預料的,列印出’nono’,由於沒有額外的yield,所以將直接丟擲StopIteration。
1 2 3 4 5 |
nono Traceback (most recent call last): File "F:python workspacePytestsrccs.py", line 170, in s.next() StopIteration |
看下面程式碼,理解throw方法,throw主要是向生成器傳送異常。
1 2 3 4 5 6 7 8 9 10 |
def mygen(): try: yield 'something' except ValueError: yield 'value error' finally: print 'clean' #一定會被執行 gg=mygen() print gg.next() #something print gg.throw(ValueError) #value error clean |
呼叫gg.next很明顯此時輸出‘something’,並在yield ‘something’暫停,此時向gg傳送ValueError異常,恢復執行環境,except 將會捕捉,並輸出資訊。
理解了這些,我們就可以向協同程式發起攻擊了,所謂協同程式也就是是可以掛起,恢復,有多個進入點。其實說白了,也就是說多個函式可以同時進行,可以相互之間傳送訊息等。
這裡有必要說一下multitask模組(不是標準庫中的),看一段multitask使用的簡單程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def tt(): for x in xrange(4): print 'tt'+str(x) yield def gg(): for x in xrange(4): print 'xx'+str(x) yield t=multitask.TaskManager() t.add(tt()) t.add(gg()) t.run() |
結果:
1 2 3 4 5 6 7 8 |
tt0 xx0 tt1 xx1 tt2 xx2 tt3 xx3 |
如果不是使用生成器,那麼要實現上面現象,即函式交錯輸出,那麼只能使用執行緒了,所以生成器給我們提供了更廣闊的前景。
如果僅僅是實現上面的效果,其實很簡單,我們可以自己寫一個。主要思路就是將生成器物件放入佇列,執行send(None)後,如果沒有丟擲StopIteration,將該生成器物件再加入佇列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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實現的肯定不止這個功能,有興趣的童鞋可以看下原始碼,還是比較簡單易懂的。
#增補 2014/5/21
之前我在南京面試Python時遇到這麼一道題目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def thread1(): for x in range(4): yield x def thread2(): for x in range(4,8): yield x threads=[] threads.append(thread1()) threads.append(thread2()) def run(threads): #寫這個函式,模擬執行緒併發 pass run(threads) |
如果上面class Task看懂了,那麼這題很簡單,其實就是考你用yield模擬執行緒排程,解決如下:
1 2 3 4 5 6 7 8 |
def run(threads): for t in threads: try: print t.next() except StopIteration: pass else: threads.append(t) |