標籤:
這是一系列的文章,會從基礎開始一步步的介紹Python中的Generator以及coroutine(協程)(主要是介紹coroutine),並且詳細的講述了Python中coroutine的各種進階用法,最後會用coroutine實現一個簡單的多任務的作業系統。
其實也是看完這篇文章的學習筆記吧!O(∩_∩)O
產生器(Generator)
什麼是產生器?在Python中,產生器(Generator)是一個帶有yield關鍵字的函數
1 def gene(): 2 a = 1 3 print "開始執行gene(),a = ",a 4 a += 1 5 yield "rio_2607" 6 print "回到gene(),再辭開始執行,a = ",a 7 a += 2 8 yield "uestc" 9 print "又回到了gene(),a = ",a10 yield "emc"
gene()就是一個產生器,因為函數的定義中有yield關鍵字。那麼產生器跟普通的函數有什麼區別呢?
當調用gene()函數的時候,不會立即執行gene()函數中的代碼,而是返回一個產生器對象(generator object):
1 >>> def gene(): 2 a = 1 3 print "開始執行gene(),a = ",a 4 a += 1 5 yield "rio_2607" 6 print "回到gene(),再辭開始執行,a = ",a 7 a += 2 8 yield "uestc" 9 print "又回到了gene(),a = ",a10 yield "emc"11 12 13 >>> g = gene()14 >>> type(g)15 <type ‘generator‘>
可以看到,g是一個generator類型的對象。那麼什麼時候會執行函數的代碼呢?答:當調用產生器對象的next()函數時就會開始執行函數定義中的代碼。
但是跟普通函數一旦開始執行就會一直執行直到結束不同,產生器函數會一直往下執行,但是一旦碰到yield關鍵字,就會返回yield關鍵字後面的資料,把函數當前的所有狀態封存起來,然後暫停函數的執行,在產生器對象的next()函數再一次被調用的時候,會接著上一次暫停地方繼續往下執行,直到碰到了下一個yield關鍵字或者函數的代碼執行完畢
>>> g = gene()>>> type(g)<type ‘generator‘>>>> g.next()開始執行gene(),a = 1‘rio_2607‘>>> g.next()回到gene(),再辭開始執行,a = 2‘uestc‘>>> g.next()又回到了gene(),a = 4‘emc‘
可以看到,第一次調用g.next()函數時,函數內部的代碼才開始執行,當執行到yield "rio_2607"這一句代碼時,會返回"rio_2607",然後函數暫停執行。然後當再次調用next函數的時候,gene()函數會接著往下面執行,可以看到,這時列印出來的a=2,保持了函數上一次離開時候的資料,當碰到yield "uestc"這一句時,函數會再次停止執行,封存此時函數內的資料。當再一次調用next()函數的時候,gene()會接著上次的狀態,在上次暫停地方繼續往下執行,可以看到,此時列印輸出了a=4,碰到yield之後再次暫停執行。
當產生器執行完畢後,再一次調用next()時,函數會拋出StopIteration異常
>>> g.next()又回到了gene(),a = 4‘emc‘>>> g.next()Traceback (most recent call last): File "<pyshell#13>", line 1, in <module> g.next()StopIteration
產生器運算式(Generator Expresisions)
產生器運算式(Generator Expresisions)類似於列表推導式(list comprehension)
ge = (x * 2 for x in a)
其中(x * 2 for x in a)就是產生器運算式,這個運算式會返回一個產生器對象:
>>> ge = (x * 2 for x in a)>>> ge<generator object <genexpr> at 0x01EA0A30>
在for迴圈中,for迴圈會自動調用產生器對象的next()函數並處理StopIteration異常:
>>> ge<generator object <genexpr> at 0x01EA0A30>>>> for i in ge: print i 2468
說了那麼多,那麼產生器除了實現迭代器(Iteration)之外,還有有什麼作用呢?
我們有這麼一個web server上面的log檔案,資料大概是這樣的
77.81.4.30 - - [24/Feb/2008:02:17:53 -0600] "GET /favicon.ico HTTP/1.1" 404 13324.1.247.118 - - [24/Feb/2008:02:20:25 -0600] "GET /dynamic/ HTTP/1.1" 200 510524.1.247.118 - - [24/Feb/2008:02:20:26 -0600] "GET /favicon.ico HTTP/1.1" 404 13324.1.247.118 - - [24/Feb/2008:02:20:26 -0600] "GET /favicon.ico HTTP/1.1" 404 133122.117.168.219 - - [24/Feb/2008:02:22:06 -0600] "GET /ply/ HTTP/1.1" 304 -122.117.168.219 - - [24/Feb/2008:02:22:06 -0600] "GET /ply/bookplug.gif HTTP/1.1" 304 -122.117.168.219 - - [24/Feb/2008:02:22:08 -0600] "GET /ply/example.html HTTP/1.1" 304 -89.182.136.236 - - [24/Feb/2008:02:23:04 -0600] "GET /ply/ HTTP/1.1" 200 801889.182.136.236 - - [24/Feb/2008:02:23:05 -0600] "GET /ply/bookplug.gif HTTP/1.1" 200 2390389.182.136.236 - - [24/Feb/2008:02:23:05 -0600] "GET /favicon.ico HTTP/1.1" 404 13366.249.65.37 - - [24/Feb/2008:02:23:29 -0600] "GET /papers/SIAM97/SIAM97.pdf HTTP/1.1" 200 188949117.198.144.124 - - [24/Feb/2008:02:23:50 -0600] "GET /ply/ply.html HTTP/1.1" 200 97238117.198.144.124 - - [24/Feb/2008:02:23:53 -0600] "GET /favicon.ico HTTP/1.1" 404 133
每一行的最後一列要麼表示一個位元組資料,要麼為-,表示位元組資料未知
現在我們要統計檔案中記錄的所有的位元組資料大小
python中常規的寫法是這樣的,在一個for迴圈中,每次處理一行資料:
1 def non_generator_func(): 2 ‘‘‘ 3 分析web server的log檔案來判斷所有傳輸的位元組數 4 Non-Generator的寫法:用一個for迴圈 5 :return: 6 ‘‘‘ 7 wwwlog = open("access-log") 8 total = 0 9 10 for line in wwwlog:11 # 擷取位元組數的字串表示12 bytestr = line.rsplit(None, 1)[1]13 if bytestr != "-":14 total += int(bytestr)15 16 print "Total", total
現在來看看使用Generator的風格編寫的代碼:
1 def generator_func():2 wwwlog = open("access-log")3 # 採用產生器運算式(Generator expression),返回一個Generator對象4 bytecolumn = (line.rsplit(None, 1)[1] for line in wwwlog)5 bytes = (int(x) for x in bytecolumn if x != "-")6 7 # 最後一步才進行計算8 print "Total", sum(bytes)
可以看出,使用Generator,可以編寫更少的代碼,還會有跟普通的Python編程完全不一樣的編程風格。
關於Python中的產生器,Python函數式編程指南(四):產生器這篇部落格講的挺好的,大家可以看下這篇部落格。
Python進階編程之產生器(Generator)與coroutine(一):Generator