python实现协程(二)

一. 预激活协程的装饰器

       调用协程函数后,返回的是一个协程对象,函数本身并不会执行。所以在调用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个方法,显示地把异常发给协程:

  1. generator.throw(exc_type[, exc_value[, traceback]]):使生成器在暂停的yield表达式处抛出指定异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,而产出的值会成为调用generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。
  2. 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的用法。

发布了132 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Geroge_lmx/article/details/105156944