Python進階編程之產生器(Generator)與coroutine(二):coroutine介紹

來源:互聯網
上載者:User

標籤:

原創作品,轉載請註明出處:點我

上一篇文章Python進階編程之產生器(Generator)與coroutine(一):Generator中,我們介紹了什麼是Generator,以及寫了幾個使用Generator Function的樣本,這一小節,我們會介紹Python的coroutine,以及會有一個小例子,再接下來的文章中會以代碼的形式一步步介紹coroutine的進階用法。

coroutine(協程)

什麼是coroutine?coroutine跟Generator有什麼區別?下面先看一段代碼: 

1 def grep_co(pattern):2     print "Lookin for %s" % pattern3     while True:4         # 執行完下面這句,函數掛起,等待接收資料,通過send()函數5         # yield line6         line = (yield )7         if pattern in line:8             print line

 

grep_co就是一個coroutine,從代碼的角度來看,coroutine跟generator的唯一區別就是在第6行,coroutine是

line = (yield)

而Generator是:

yield line

那麼這兩者有什麼區別呢?先別急,我們接著往下看:

在Python2.5及之後的版本中,yield可以用作為運算式,就是gerp_co()函數中的這種用法。那麼問題來了,gerp_co()函數中的line的值應該是多少?這個值是誰給他的?

答案很簡單:line的值是我們(grep_co())的調用者發送過去的。怎麼發送的?Easy!使用send(value)函數即可。先來看下執行效果:

 1 >>> def grep_co(pattern): 2     print "Looking for %s" %pattern 3     while True: 4         line = (yield) 5         if pattern in line: 6             print line 7  8              9 >>> g = grep_co("python")10 >>> g11 <generator object grep_co at 0x01FA3B98>

跟Generator一樣,當調用gerp_co("python")的時候,並不會立即執行grep_co函數,而是會返回一個generator類型的對象,同樣是在這個對象調用了next()的時候才會開始執行這個函數:

>>> g.next()Looking for python

調用了next()函數之後,函數開始執行,執行到第6行,也就是line = (yield)這一行代碼的時候,碰到了yield關鍵字,跟Generator一樣,此時,整個函數會掛起,由於yield後面沒有跟隨其他的變數(一般情況下,coroutine中的yield語句也不會跟隨傳回值,這個後面會講到),所以此時不會返回任何資料,只是單純的儲存執行環境並掛起暫停執行。

既然coroutine跟Generator一樣碰到yield關鍵字會掛起,那麼是不是也跟Generator一樣調用next()函數繼續執行呢?其實你如果能夠這樣想我會很高興,說明你有在認真的看,O(∩_∩)O~。不幸的是想法是好的,可惜是錯的,O(∩_∩)O哈哈~,應該使用send(value)函數。接著上面往下走,上面調用了g.next(),函數開始執行,碰到了yield關鍵字,函數掛起暫停執行,現在調用g.send("I love python")函數,執行結果如下:

>>> g.send("Hello,I love python")Hello,I love python

可以看到,send 函數有一個參數,這個參數就是傳遞個line這個變數的。調用了send("I love python")這個函數之後,grep_co()這個函數會接著上次掛起的地方往下執行,也就是在第六行line = (yield)這個地方,send("I love python")函數的參數會被傳遞給line這個變數,然後接著往下執行,直到執行完畢或者再次碰到yield關鍵字。在這個例子中,line的值是"I love pyhton",pattern的值是"python",if判斷為真,列印輸出line,接著往下執行,因為是在一個無限迴圈當中,再次碰到了yield這個關鍵字,掛起並暫停。所以我們會看到上面的執行結果。

>>> g.send("Life is short,Please use Python")>>> g.send("Life is short,Please use python")Life is short,Please use python

我們再繼續調用send(value)函數,會重複上面的執行過程。

講了這麼多,那麼什麼才是coroutine呢?我相信聰明的你應該已經猜到了:

所謂的coroutine,也就是一個包含有yield關鍵字的函數,但是跟Generator不同的是,coroutine會以value = (yield)的方式使用yield關鍵字,並且接受調用者通過send(value)函數發送過來的資料,然後消費這個資料(consume the value)。

在使用coroutine,有一點很需要注意的就是:所有的coroutine必須要先調用.next()或者send(None)才行。在調用send傳入非None值前,產生器必須處於掛起狀態,否則將拋出異常。當然,也可以使用.next()恢複產生器,只不過此時coroutine接收到的value為None。

可以調用.close()關閉coroutine。關閉coroutine之後,再次調用.nect()或者.send(value)之後會拋出異常。

>>> g.close()>>> g.send("corotuine has already closed")Traceback (most recent call last):  File "<pyshell#16>", line 1, in <module>    g.send("corotuine has already closed")StopIteration>>> 

.close()會拋出GeneratorExit異常,我們可以在代碼中捕獲並處理這個異常,而且一般情況下也應該處理這個異常。

1 def grep(pattern):2     print "Looking for %s" %pattern3     try:4         while True:5             line = (yield)6             if pattern in line:7                 print line8     except GeneratorExit:9         print "Going away.Goodbye"

當然也可以通過throw()函數在產生器內部拋出一個指定的異常。

>>> g.send("Life is short,please use python")Life is short,please use python>>> g.throw(RuntimeError,"You‘ar hosed")Traceback (most recent call last):  File "<pyshell#14>", line 1, in <module>    g.throw(RuntimeError,"You‘ar hosed")  File "<pyshell#10>", line 5, in grep    line = (yield)RuntimeError: You‘ar hosed

好了,coroutine已經介紹的差不多了,我們可以看到coroutine可以很方便的掛起和執行,也有多個人口點和出口點,而普通的函數一般只有一個進入點和出口點。

Generator和coroutine用起來很像,但是僅此而已,Generator和coroutine是兩個完全不相同的概念。Generator產生(返回)資料用來在迭代(iterator)中使用,而coroutine則是需要其他的地方發送資料過來,從而消費資料(consume value)。

接下來講的是使用coroutine要注意的地方:

第一,就是千萬別忘記了在使用coroutine前要先調用.next()函數。但是這一點經常容易忘記,所以可以使用一個function decorator.

1 # 作為裝飾器用,因為經常容易會忘記調用.next()函數2 def coroutine(func):3     def start(*args,**kargs):4         cr = func(*args,**kargs)5         cr.next()6         return cr7     return start

第二:最好是不要把Generator和coroutine混合在一起用,也就是receive_value = (yield return_value)這種方式來用。因為這會很難以理解,而且也會出現某些很詭異的情況。先看代碼:

1 def countdown_co(n):2     print "Counting down from ",n3     while n >= 0:4         newvalue = (yield n)5         # 如果接收到了newvalue,則重新設定n6         if newvalue is not None:7             n = newvalue8         else:9             n -= 1

代碼很簡單,同時使用了coroutine和Generator,詭異的情況發生了。先是寫一個函數test_countdown_co():

1 def test_countdown_co():2     c = countdown_co(5)3     for n in c:4         print n5         if 5 == n:6             c.send(3)

然後在IDLE終端調用這個函數,可以看到函數的執行結果為:

>>> test_countdown_co()Counting down from  55210

現在,我們在IDLE終端直接輸入上面的test_countdown_co()代碼來測試countdown_co()函數:

>>> c = countdown_co(5)>>> for n in c:    print n    if 5 == n:        c.send(3)        Counting down from  553210>>> 

可以看到一樣的代碼,執行結果卻不一樣,好詭異啊!到現在我都沒有想明白這是為什麼。如果有誰知道原因,請告訴我,O(∩_∩)O謝謝!

好了!這一篇介紹coroutine的Blog也寫好了。接下來的文章會以完整的代碼的形式來介紹coroutine的一些進階用法。敬請期待。O(∩_∩)O哈哈~

 

Python進階編程之產生器(Generator)與coroutine(二):coroutine介紹

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.