一. 預(yù)激活協(xié)程的裝飾器
調(diào)用協(xié)程函數(shù)后,返回的是一個協(xié)程對象伙单,函數(shù)本身并不會執(zhí)行胯杭。所以在調(diào)用 send
方法前,必須使用 next()
或 send(None)
來預(yù)激活協(xié)程函數(shù)膜宋,使協(xié)程函數(shù)執(zhí)行到第一個 yield
表達式窿侈,處于暫停狀態(tài)。
為了簡化操作秋茫,下面我們定義一個預(yù)激活的裝飾器:
from functools import wraps
def coroutine(func):
"""裝飾器:向前執(zhí)行到第一個yield表達式史简,預(yù)激func"""
@wraps(func)
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen)
return gen
return primer
@coroutine
def average():
total = 0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count
我們定義的裝飾中,把被裝飾的生成器函數(shù)替換成這里的 primer
函數(shù)肛著,調(diào)用 primer
函數(shù)時圆兵,返回預(yù)激活后的生成器。接下來使用 @coroutine
裝飾器裝飾 average()
協(xié)程函數(shù)枢贿。
運行結(jié)果:調(diào)用上述模塊中定義的 average()
函數(shù)創(chuàng)建一個生成器對象殉农,因為在 coroutine
裝飾器的 primer
函數(shù)中已經(jīng)預(yù)激活了生成器,所以 getgeneratorstate(coro_ava)
返回 GEN_SUSPENDED
狀態(tài)局荚,因此協(xié)程已經(jīng)準備好超凳,可以接收值了。
很多框架都提供了處理協(xié)程的特殊裝飾器耀态,不過不是所有裝飾器都用于預(yù)激協(xié)程轮傍,有些會提供其它服務(wù),例如勾入事件循環(huán)首装。 比如說创夜,異步網(wǎng)絡(luò)庫 Tornado
提供了 tornado.gen
裝飾器。
另外仙逻,使用 yield from
句法調(diào)用協(xié)程時驰吓,會自動預(yù)激。python
標準庫里的 asyncio.coroutine
裝飾器不會預(yù)激協(xié)程系奉,因此能兼容 yield from
句法檬贰。
二. 終止協(xié)程和異常處理
協(xié)程中未處理的異常會向上冒泡,傳給 next()
函數(shù)或 send()
方法的調(diào)用方(即觸發(fā)協(xié)程的對象)喜最。因此偎蘸,未處理的異常會導致協(xié)程終止。以上面的 coro_ava
協(xié)程實例為例:
由于發(fā)送給 average()
函數(shù)的值不是數(shù)字,因此協(xié)程內(nèi)有異常拋出迷雪。由于在協(xié)程內(nèi)沒有處理異常限书,協(xié)程會終止。如果視圖重新激活協(xié)程章咧,會拋出 StopIteration
異常倦西。
上述示例暗示了終止協(xié)程的一種方式,發(fā)送一個異常給協(xié)程赁严。Python2.5 開始扰柠,客戶端代碼可以在生成器對象上調(diào)用 2 個方法,顯示地把異常發(fā)給協(xié)程:
generator.throw(exc_type[, exc_value[, traceback]])
:使生成器在暫停的yield
表達式處拋出指定異常疼约。如果生成器處理了拋出的異常卤档,代碼會向前執(zhí)行到下一個yield
表達式,而產(chǎn)出的值會成為調(diào)用generator.throw
方法得到的返回值程剥。如果生成器沒有處理拋出的異常劝枣,異常會向上冒泡,傳到調(diào)用方的上下文中织鲸。generator.close()
:使生成器在暫停的yield
表達式處拋出GeneratorExit
異常舔腾。如果生成器沒有處理這個異常,或者拋出了StopIterator
異常搂擦,調(diào)用方不會報錯稳诚。生成器拋出的其它異常會向上冒泡,傳給調(diào)用方瀑踢。
示例代碼 在協(xié)程中處理異常
class DemoException(Exception):
"""為這次演示定義的異常類型"""
def demo_exc_handler():
print('-> coroutine started')
while True:
try:
x = yield
except DemoException:
print('*** DemoException handled. Continue ***')
else:
print(f'-> coroutine received:{x}')
raise RuntimeError('This line should never run.')
演示 1 close
終止協(xié)程扳还,不會有異常
演示 2 如果把 DemoException
異常傳入 demo_exc_handler
,不會導致協(xié)程終止
演示 3 如果無法處理傳入的異常丘损,協(xié)程會終止
如果不管協(xié)程如何結(jié)束都想做些清理工作普办,要把協(xié)程定義體中相關(guān)的代碼放入 try / finally
塊中工扎。
下面我們改進上述 demo_exc_handler
協(xié)程函數(shù)的定義徘钥,使用 try / finally
在協(xié)程終止時執(zhí)行操作:
class DemoException(Exception):
"""為這次演示定義的異常類型"""
def demo_finally():
print('-> coroutine started')
try:
while True:
try:
x = yield
except DemoException:
print('*** DemoException handled. Continue ***')
else:
print(f'-> coroutine received:{x}')
finally:
print('-> coroutine ending')
演示 使用 try / finally
在協(xié)程終止時執(zhí)行操作
Python 3.3 引入 yield from
結(jié)構(gòu)的主要原因之一與把異常傳入嵌套的協(xié)程有關(guān)。另一個原因是讓協(xié)程更方便的返回值肢娘,下節(jié)將介紹讓協(xié)程返回值以及 yield from
的用法呈础。