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