0
点赞
收藏
分享

微信扫一扫

Python 实现协程(二)

杰克逊爱学习 2021-09-29 阅读 120

一. 预激活协程的装饰器

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

举报

相关推荐

0 条评论