一. 預激活協程的裝飾器
調用協程函數後,返回的是一個協程對象,函數本身並不會執行。所以在調用send方法前,必須使用next()或send(None)來預激活協程函數,是協程函數執行到第一個yield表達式,處於暫停狀態。爲了簡化操作,下面我們定義一個預激活的裝飾器:
from functools import wraps
def coroutine(func):
"""裝飾器:向前執行到第一個yield表達式,預激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
我們定義的裝飾中,把被裝飾的生成器函數替換成這裏的primer函數,調用primer函數時,返回預激活後的生成器。接下來使用@coroutine裝飾器裝飾average()協程函數。
運行結果:調用上述模塊中定義的average()函數創建一個生成器對象,因爲在coroutine裝飾器的primer函數中已經預激活了生成器,所以getgeneratorstate(coro_ava)返回“GEN_SUSPENDED”狀態,因此協程已經準備好,可以接收值了。
很多框架都提供了處理協程的特殊裝飾器,不過不是所有裝飾器都用於預激協程,有些會提供其它服務,例如勾入事件循環。 比如說,異步網絡庫Tornado提供了tornado.gen裝飾器。
另外,使用yield from句法調用協程時,會自動預激。python標準庫裏的asyncio.coroutine裝飾器不會預激協程,因此能兼容yield from句法。
二. 終止協程和異常處理
協程中未處理的異常會向上冒泡,傳給next()函數或send()方法的調用方(即觸發協程的對象)。因此,未處理的異常會導致協程終止。以上面的coro_ava協程實例爲例:
由於發送給average()函數的值不是數字,因此協程內有異常拋出。由於在協程內沒有處理異常,協程會終止。如果視圖重新激活協程,會拋出StopIteration異常。
上述示例暗示了終止協程的一種方式,發送一個異常給協程。python2.5開始,客戶端代碼可以在生成器對象上調用2個方法,顯示地把異常發給協程:
- generator.throw(exc_type[, exc_value[, traceback]]):使生成器在暫停的yield表達式處拋出指定異常。如果生成器處理了拋出的異常,代碼會向前執行到下一個yield表達式,而產出的值會成爲調用generator.throw方法得到的返回值。如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調用方的上下文中。
- generator.close():使生成器在暫停的yield表達式處拋出GeneratorExit異常。如果生成器沒有處理這個異常,或者拋出了StopIterator異常,調用方不會報錯。生成器拋出的其它異常會向上冒泡,傳給調用方。
示例代碼 學習在協程中處理異常
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終止協程,不會有異常
演示2 如果把DemoException異常傳入demo_exc_handler,不會導致協程終止
演示3 如果無法處理傳入的異常,協程會終止
如果不管協程如何結束都想做些清理工作,要把協程定義體中相關的代碼放入try/finally塊中。下面我們改進上述demo_exc_handler協程函數的定義,使用try/finally在協程終止時執行操作:
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在協程終止時執行操作
python 3.3引入yield from結構的主要原因之一與把異常傳入嵌套的協程有關。另一個原因是讓協程更方便的返回值,下節將介紹讓協程返回值以及yield from的用法。