Python高級編程之生成器(Generator)與coroutine(二):coroutine介紹


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

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

coroutine(協程)

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

1 def grep_co(pattern):
2     print "Lookin for %s" % pattern
3     while True:
4         # 執行完下面這句,函數掛起,等待接收數據,通過send()函數
5         # yield line
6         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 >>> g
11 <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" %pattern
3     try:
4         while True:
5             line = (yield)
6             if pattern in line:
7                 print line
8     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 cr
7     return start

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

1 def countdown_co(n):
2     print "Counting down from ",n
3     while n >= 0:
4         newvalue = (yield n)
5         # 如果接收到了newvalue,則重新設置n
6         if newvalue is not None:
7             n = newvalue
8         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 n
5         if 5 == n:
6             c.send(3)

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

>>> test_countdown_co()
Counting down from  5
5
2
1
0

現在,我們在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  5
5
3
2
1
0
>>> 

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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM