最后更新于
最后更新于
Python中, 可以通过使用yield定义的生成器, 实现协程.
首先定义一个生成器:
形式如同定义函数, 如果定义体中含有yield关键字, 则就是一个生成器的定义体. 创建一个生成器对象:
输出为:
协程有四种状态:
GEN_CREATED: 等待开始执行
GEN_RUNNING: 解释器正在执行
GEN_SUSPENDED: 在yield表达式处暂停
GEN_CLOSED: 执行结束
可以使用inspect包中的inspect.getgeneratorstate
函数查看某个协程的状态:
输出为:
一个刚创建的生成器, 其状态为GEN_CREATED, 此时的协程还没有被激活, 需要经过预激(prime)操作, 让协程向前执行到第一个yield表达式, 准备好作为活跃的协程使用. 预激的方法有两种:
调用next(my_coro)
方法
使用协程的send方法, 并传递一个None, my_coro.send(None)
输出为:
可以看到, 此时的生成器执行到yield的表达式处, 并将控制权交还给了调用方.
查看此时协程的状态:
输出为:
即状态变成了GEN_SUSPENDED, 代表在yield表达式处暂停.
继续调用:
输出为:
可以看出:
send方法输入的参数, 会被赋给yield表达式所在行的左侧的变量, 此处将42
赋给了参数x
生成器执行完之后, 抛出StopIteration
错误. 可以由调用方捕捉这个错误, 进行相应逻辑的处理
查看此时协程的状态:
输出为:
生成器, 也即协程已经正常结束了.
如果协程没有预激, 就直接使用send
方法传递参数, 会报如下的错位:
输出为:
生成器在执行到yield表达式暂停时, 会将yield表达式执行后的结果, 返回给调用方. 看下面的例子:
输出为:
可以看到激活操作, 执行了第一个yield表达式, 将生成器中a
的值14
返回给了调用方, 并等待send
操作传入值. 继续调用send
方法, 会将send中指定的参数传递给b
, 向下执行, 将yield表达式a+b
的值返回:
输出为:
结果如同预期, 将a+b=42
的结果返回. 继续调用, 协程执行完毕.
输出为
整个过程分三步:
调用next(my_coro2), 打印a
的值, 然后执行yield a, 产出数字14, 返回
调用my_coro2.send(28), 把28赋值给b, 打印b
的值, 然后执行 yield a + b, 产生数字42, 返回
调用my_coro2.send(99), 把99赋值给c, 打印c
的值, 产生数字99, 返回
新创建的生成器对象, 每次都要手动进行预激, 比较麻烦, 可以用装饰器进行预激操作:
定义生成器:
直接创建的生成器对象就已经是激活的状态:
输出为:
协程向调用方
协程运行过程中的异常, 会向上冒泡, 传递给next
函数或者send
方法的调用方, 调用方如果没有对其进行处理, 就会导致错误终止.
结果为:
调用方向协程
上面的例子, 换个角度, 可以看做是传入特别的参数, 手动使协程退出. 而生成器中有两个特别的方法可以实现这种事情.
throw
方法
generator.throw(exc_type[, exc_value[, traceback]])
, 这个方法使生成器在暂停的yield表达式处抛出指定的异常, 如果生成器处理了抛出的异常, 代码会继续向下进行, 直到遇到下一个yield表达式或整个代码运行完毕. 当然, 如同send
方法一样, 下一个yield产出的值会成为调用throw方法得到的返回值. 如果没有处理, 则向上冒泡, 直接抛出
close
方法
generator.close()
, 生成器在暂停的yield表达式处抛出GeneratorExit异常, 如果生成器没有处理这个异常或者抛出了StopIteration异常, 调用方不会报错, 如果收到GeneratorExit异常, 生成器一定不能产出值, 否则解释器会抛出RuntimeError异常.
输出为:
查看当前状态:
结果为:
如果传入DemoException, 因为做了异常处理, 协程不会中止:
结果为:
查看当前状态:
结果为:
由于协程执行完毕后向外报StopIteration
错误, 对于有return
值的协程生成器, 如何获取它return的值, 是需要考察的问题.
结果为:
可以看到奇特的一点, return表达式的值, 会作为StopIteration异常的一个属性值被带出, 这样做是为了保留生成器对象耗尽时抛出StopIteration异常的行为. 如果要获取这个值, 就需要如下的操作:
就能得到结果: