一. 预激活协程的装饰器
调用协程函数后,返回的是一个协程对象,函数本身并不会执行。所以在调用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的用法。