zl程序教程

您现在的位置是:首页 >  后端

当前栏目

流畅的python第十六章协程学习记录

Python学习 记录 协程 流畅 第十六章
2023-09-27 14:26:13 时间

从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数。可是,在协
程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可
以不产出——如果 yield 关键字后面没有表达式,那么生成器产出 None。协程可能会从
调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是
next(...) 函数。通常,调用方会把值推送给协程。
yield 关键字甚至还可以不接收或传出数据。不管数据如何流动,yield 都是一种流程控
制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激
活其他的协程。
从根本上把 yield 视作控制流程的方式,这样就好理解协程了。

生成器作为协程使用时的行为和状态
使用装饰器自动预激协程
调用方如何使用生成器对象的 .close() 和 .throw(...) 方法控制协程
协程终止时如何返回值
yield from 新句法的用途和语义
使用案例——使用协程管理仿真系统中的并发活动

协程可以身处四个状态中的一个。当前状态可以使用
inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字符串中的一个。
'GEN_CREATED'
  等待开始执行。
'GEN_RUNNING'
  解释器正在执行。

'GEN_SUSPENDED'
  在 yield 表达式处暂停。
'GEN_CLOSED'
  执行结束。

因为 send 方法的参数会成为暂停的 yield 表达式的值,所以,仅当协程处于暂停状态时
才能调用 send 方法,例如 my_coro.send(42)。不过,如果协程还没激活(即,状态是
'GEN_CREATED'),情况就不同了。因此,始终要调用 next(my_coro) 激活协程——也
可以调用 my_coro.send(None),效果一样。

在创建好协程之后,需要调用next激活协程,否则会报错。

最先调用 next(my_coro) 函数这一步通常称为“预激”(prime)协程(即,让协程向前执
行到第一个 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

终止协程和异常处理

协程中未处理的异常会向上冒泡,传给 next 函数或 send 方法的调用方(即触发协程的
对象)

终止协程的一种方式:发送某个哨符值,让协程退出。内置的 None 和
Ellipsis 等常量经常用作哨符值。Ellipsis 的优点是,数据流中不太常有这个值。我
还见过有人把 StopIteration 类(类本身,而不是实例,也不抛出)作为哨符值;也就
是说,是像这样使用的:my_coro.send(StopIteration)。

从 Python 2.5 开始,客户代码可以在生成器对象上调用两个方法,显式地把异常发给协
程。
这两个方法是 throw 和 close。
generator.throw(exc_type[, exc_value[, traceback]])
  致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成器处理了抛出的异
常,代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw
方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上

下文中。

generator.close()
  致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。如果生成器没有处
理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会报
错。如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出
RuntimeError 异常。生成器抛出的其他异常会向上冒泡,传给调用方。

如何使用close和throw

class DemoException(Exception):
"""为这次演示定义的异常类型。"""
def demo_exc_handling():
    print('-> coroutine started')
    while True:
        try:
            x = yield
        except DemoException: ➊
            print('*** DemoException handled. Continuing...')
        else: ➋
            print('-> coroutine received: {!r}'.format(x))
    raise RuntimeError('This line should never run.')

获取协程的返回值虽然要绕个圈子,但这是 PEP 380 定义的方式,当我们意识到这一点之
后就说得通了:yield from 结构会在内部自动捕获 StopIteration 异常。这种处理方
式与 for 循环处理 StopIteration 异常的方式一样:循环机制使用用户易于理解的方式
处理异常。对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把
value 属性的值变成 yield from 表达式的值。可惜,我们无法在控制台中使用交互的方
式测试这种行为,因为在函数外部使用 yield from(以及 yield)会导致句法出错。

使用yield from

for c in 'ab':
    yield c
等价于
yield from 'ab'

yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因
此,x 可以是任何可迭代的对象。

yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起
来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中
添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职
责。

yield from的意义