流畅的python——16 协程

十六、协程

生成器如何进化成协程

用作协程的生成器的基本行为

协程演示

In [51]: def s_c():
    ...:     print('c start')
    ...:     x = yield
    ...:     print('c received:',x)
    ...:

In [52]: c = s_c()

In [53]: c
Out[53]: <generator object s_c at 0x00000221EF5DB5C8>

In [54]: c.send(111)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-54-ffa2730868ca> in <module>
----> 1 c.send(111)

TypeError: can't send non-None value to a just-started generator

In [55]: next(c)  # 首先调用 next ,因为生成器还没启动,没在 yield 暂停,不能发送数据。
c start

In [56]: next(c)  # yield 表达式 为 发送的值。协程会恢复,一直运行到下一个 yield 表达式,或者终止。
c received: None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-56-e846efec376d> in <module>
----> 1 next(c)

StopIteration:

In [57]: c.send(111)  # # yield 表达式 为 发送的值。
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-57-ffa2730868ca> in <module>
----> 1 c.send(111)

StopIteration:

协程可以身处四个状态中的一个。当前状态可以使用 inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字符串中的一个。

'GEN_CREATED'

  等待开始执行。

'GEN_RUNNING'

  解释器正在执行。

​ 只有在多线程应用中才能看到这个状态。此外,生成器对象在自己身上调用 getgeneratorstate 函数也行,不过这样做没什么用。

'GEN_SUSPENDED'

  在 yield 表达式处暂停。

'GEN_CLOSED'

  执行结束。

In [59]: import inspect

In [60]: inspect.getgeneratorstate(c)
Out[60]: 'GEN_CLOSED'

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

最先调用 next(my_coro) 函数这一步通常称为“预激”(prime)协程(即,让协程向前执行到第一个 yield 表达式,准备好作为活跃的协程使用)。

In [62]: def cc(a):
    ...:     print('c start')
    ...:     b = yield a
    ...:     print('rsv:',b)
    ...:     c = yield a + b
    ...:     print('rsv:' ,c)
    ...:

In [63]: c2 = cc(1)

In [64]: inspect.getgeneratorstate(c2)
Out[64]: 'GEN_CREATED'

In [65]: c2.send(None)
c start
Out[65]: 1

In [66]: inspect.getgeneratorstate(c2)
Out[66]: 'GEN_SUSPENDED'

In [67]: c2.send(2)
rsv: 2
Out[67]: 3

In [68]: c2.send(3)
rsv: 3
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-68-e3c5dc9e41ab> in <module>
----> 1 c2.send(3)

StopIteration:

In [69]: inspect.getgeneratorstate(c2)
Out[69]: 'GEN_CLOSED'

In [70]: c3 = cc(2)

In [71]: next(c3)
c start
Out[71]: 2

In [72]: dd = c3.send(3)
rsv: 3

In [73]: dd
Out[73]: 5

关键一点是,协程在 yield 关键字所在的位置暂停执行。赋值语句中, = 右边的代码在赋值之前执行。因此,b = yield a 这行代码,等到客户端代码再激活协程时,才会设定 b 的值。

cc 协程的执行过程分为 3 个阶段:

1 调用 next , 打印第一个消息,执行 yield a ,产出数字 a

2 调用 send(11),把 11 赋值给 b,打印第二个消息,然后执行 yield a + b,产出数字 a + 11

3 调用 send(12),把12 赋值给 c,打印第三个消息,协程终止。

注意,各个阶段都在 yield 表达式中结束,而且下一个阶段都从那一行代码开始,然后再把 yield 表达式的值赋给变量

使用协程计算移动平均值

In [80]: def avg():
    ...:     total = 0
    ...:     c = 0
    ...:     avg = None
    ...:     while True:
    ...:         term = yield avg
    ...:         total+= term
    ...:         c += 1
    ...:         avg =total / c
    ...:

# 这个无限循环表明,只要调用方不断把值发给这个协程,它就会一直接收值,然后生成结果。仅当调用方在协程上调用 .close() 方法,或者没有对协程的引用而被垃圾回收程序回收时,这个协程才会终止。
# 使用协程的好处是,total 和 count 声明为局部变量即可,无需使用实例属性或闭包在多次调用之间保持上下文。

In [81]: a = avg()

In [82]: a.send(None)

In [83]: a.send(1)
Out[83]: 1.0

In [84]: a.send(2)
Out[84]: 1.5

预激协程的装饰器

函数调用产生一个生成器——》调用完成的返回的生成器可以一直生成值——》装饰器执行预激后,返回生成器。

In [87]: def pre_next(gen):
    ...:     def inner(*args,**kwargs):  # 返回预激后的生成器
    ...:         g = gen(*args,**kwargs)
    ...:         next(g)
    ...:         return g
    ...:     return inner
    ...:

In [88]: @pre_next
    ...: def avg():
    ...:     total = 0
    ...:     c = 0
    ...:     avg = None
    ...:     while True:
    ...:         term = yield avg
    ...:         total+= term
    ...:         c += 1
    ...:         avg =total / c
    ...:

In [89]: a = avg()

In [90]: a.send(1)
Out[90]: 1.0

In [91]: b = avg()

In [92]: inspect.getgeneratorstate(b)
Out[92]: 'GEN_SUSPENDED'

使用 yield from 句法(参见 16.7 节)调用协程时,会自动预激,因此与示例 16-5 中的 @coroutine 等装饰器不兼容。

终止协程和异常处理

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

未处理的异常会导致协程终止
In [93]: b.send(1)
Out[93]: 1.0

In [94]: b.send('a')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-94-5d63b0dfa469> in <module>
----> 1 b.send('a')

<ipython-input-88-028ea1232b5b> in avg()
      6     while True:
      7         term = yield avg
----> 8         total+= term
      9         c += 1
     10         avg =total / c

TypeError: unsupported operand type(s) for +=: 'int' and 'str'

In [95]: b.send(2)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-95-19972eea9127> in <module>
----> 1 b.send(2)

StopIteration:

示例 16-7 暗示了终止协程的一种方式:发送某个哨符值,让协程退出。内置的 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 异常。生成器抛出的其他异常会向上冒泡,传给调用方。

In [1]: class DE(Exception):
   ...:     """自定义异常"""
   ...:

# 的最后一行代码不会执行,因为只有未处理的异常才会中止那个无限循环,而一旦出现未处理的异常,协程会立即终止。
In [2]: def demo_exc():
   ...:     print('c start')
   ...:     while True:
   ...:         try:
   ...:             x = yield
   ...:         except DE:
   ...:             print('DE raise')
   ...:         else:
   ...:             print('c received:{}'.format(x))
   ...:     raise RuntimeError('never run')
   ...:

In [3]: e = demo_exc()

In [4]: next(e)
c start

In [5]: next(e)
c received:None

In [6]: e.send(111)
c received:111

In [7]: e.close()  # 关闭生成器

In [8]: from inspect import getgeneratorstate

In [9]: getgeneratorstate(e)
Out[9]: 'GEN_CLOSED'

In [10]: e1 = demo_exc()

In [11]: getgeneratorstate(e1)
Out[11]: 'GEN_CREATED'

In [12]: next(e1)
c start

# 如果把 DemoException 异常传入 demo_exc_handling 协程,它会处理,然后继续运行
In [13]: e1.throw(DE)
DE raise

In [14]: getgeneratorstate(e1)
Out[14]: 'GEN_SUSPENDED'

In [15]: e1.send(22)
c received:22

# 但是,如果传入协程的异常没有处理,协程会停止,即状态变成 'GEN_CLOSED'。
In [16]: e1.throw(ZeroDivisionError)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-16-175ec079c766> in <module>
----> 1 e1.throw(ZeroDivisionError)

<ipython-input-2-981b1ab8dc67> in demo_exc()
      3     while True:
      4         try:
----> 5             x = yield
      6         except DE:
      7             print('DE raise')

ZeroDivisionError:

In [17]: getgeneratorstate(e1)
Out[17]: 'GEN_CLOSED'
# 如果不管协程如何结束都想做些清理工作,要把协程定义体中相关的代码放入 try/finally 块中
In [20]: def demo_exc():
    ...:     try:
    ...:         print('c start')
    ...:         while True:
    ...:             try:
    ...:                 x = yield
    ...:             except DE:
    ...:                 print('DE raise')
    ...:             else:
    ...:                 print('c received:{}'.format(x))
    ...:     finally:
    ...:         print('c end')

让协程返回值

In [21]: from collections import namedtuple

In [22]: Res = namedtuple('Res','c avg')

In [24]: def averager():
    ...:     t = 0
    ...:     c = 0
    ...:     while True:
    ...:         term = yield
    ...:         if term is None:
    ...:             break
    ...:         t += term
    ...:         c += 1
    ...:         avg = t/c
    ...:     return Res(c,avg)

In [25]: a = averager()

In [26]: next(a)

In [27]: a.send(1)

In [28]: a.send(2)

# 一如既往,生成器对象会抛出 StopIteration 异常。异常对象的 value 属性保存着返回的值。
In [29]: next(a)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-29-15841f3f11d4> in <module>
----> 1 next(a)

StopIteration: Res(c=2, avg=1.5)



In [54]: a = averager()

In [55]: a.send(None)

In [56]: a.send(1)

In [57]: a.send(2)

# 捕获 StopIteration 异常,获取 averager 返回的值
In [59]: try:
    ...:     a.send(None)
    ...: except StopIteration as exc:
    ...:     res = exc.value
    ...:

In [60]: res
Out[60]: Res(c=2, avg=1.5)

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

4 iPython 有个扩展——ipython-yf(https://github.com/tecki/ipython-yf),安装这个扩展后可以在 iPython 控制台中直接执行 yield from。这个扩展用于测试异步代码,可以结合 asyncio 模块使用。这个扩展已经提交为 Python 3.5 的补丁,但是没有被接受。参见 Python 缺陷追踪系统中的 22412 号工单: Towards an asyncio-enabled command line(http://bugs.python.org/issue22412)。

使用 yield from

在生成器 gen 中使用 yield from subgen() 时,subgen 会获得控制权,把产出的值传给 gen 的调用方,即调用方可以直接控制 subgen。与此同时,gen 会阻塞,等待 subgen 终止。

In [61]: def g():
    ...:     for c in 'AB':
    ...:         yield c
    ...:     for i in range(3):
    ...:         yield i
    ...:

In [65]: list(g())
Out[65]: ['A', 'B', 0, 1, 2]

# 可以改写为:

In [66]: def gen():
    ...:     yield from 'AB'
    ...:     yield from range(3)
    ...:

In [67]: list(gen())
Out[67]: ['A', 'B', 0, 1, 2]

# itertools 模块 提供了优化版的 chain 函数,使用 C 语言编写,如下是相同功能的简单实现
In [68]: def ch(*iterables):
    ...:     for i in iterables:
    ...:         yield from i
    ...:

In [69]: list(ch('ABC',range(3)))
Out[69]: ['A', 'B', 'C', 0, 1, 2]

yield from x 表达式对 x 做的事:

1 iter(x) 所以 x 可以是任何可迭代对象

yield from 结构的本质作用不仅仅是替代产出值的嵌套 for 循环

Syntax for Delegating to a Subgenerator

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

委派生成器

包含 yield from <iterable> 表达式的生成器函数。

子生成器

yield from 表达式中 <iterable> 部分获取的生成器。

调用方

调用委派生成器的客户端代码。

子生成器可能是简单的迭代器,只实现了 __next__ 方法;但是,yield from 也能处理这种子生成器。不过,引入 yield from 结构的目的是为了支持实现了 __next__sendclosethrow 方法的生成器。

    def averager():
        total = 0
        count = 0
        while True:
            x = yield
            if x is None:
                break
            total += x
            count += 1
            return Res(count, total/count)

    a = averager()
    next(a)
    b = a.send(111)  # b 是 None ,没有得到返回值
    """生成器结束了,报错:StopIteration ,返回值:存放在异常的 value 中"""
    """
    Traceback (most recent call last):
    File ".\g.py", line 52, in <module>
        b = a.send(111)
    StopIteration: Res(count=1, average=111.0)
    """
    print(b)
from collections import namedtuple

Res = namedtuple('Res','count average')

def averager():
    total = 0
    count = 0
    while True:
        x = yield
        if x is None:
            break
        total += x
        count += 1
        return Res(count, total/count)

def report(res):
    print(res)  # {'girls;kg': None, 'girls;m': None, 'boys;kg': None, 'boys;m': None}
    for k,v in sorted(res.items()):
        group, unit = k.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
            v.count, group , v.average, unit
        ))

正确的 averager :

from collections import namedtuple

Res = namedtuple('Res','count average')

# 子生成器
def averager():
    total = 0
    count = 0
    while True:
        x = yield
        if x is None:
            break
        total += x
        count += 1
    # 返回的 Result 会成为 grouper 函数中 yield from 表达式的值。
    # yield from 会处理 StopIterator 异常,返回结束的值
    return Res(count, total/count)  

# 委派生成器
def grouper(res, key):
    # 这个循环每次迭代时会新建一个 averager 实例;每个实例都是作为协程使用的生成器对象。
    while True:
        res[key] = yield from averager()

# 客户端代码,即调用方
def main(data):
    res = {}
    for k,v in data.items():
        group = grouper(res, k)
        next(group)
        for i in v:
            # 把各个 value 传给 grouper。传入的值最终到达 averager 函数中 term = yield 那一行;grouper 永远不知道传入的值是什么。
            group.send(i)
        # 把 None 传入 grouper,导致当前的 averager 实例终止,也让 grouper 继续运行,再创建一个 averager 实例,处理下一组值。
        group.send(None)
    report(res)

# 输出报告
def report(res):
    print(res)
    for k,v in sorted(res.items()):
        group, unit = k.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
            v.count, group , v.average, unit
        ))

data = {
 'girls;kg':
 [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
 'girls;m':
 [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
 'boys;kg':
 [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
 'boys;m':
 [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}

if __name__ == '__main__':
    main(data)



"""
(el_app) PS C:\Users\WangLin\Desktop\version8> python .\g.py
{'girls;kg': Res(count=10, average=42.040000000000006), 'girls;m': Res(count=10, average=1.4279999999999997), 'boys;kg': Res(count=9, average=40.422222222222224), 'boys;m': Res(count=9, average=1.3888888888888888)}
 9 boys  averaging 40.42kg
 9 boys  averaging 1.39m
10 girls averaging 42.04kg
10 girls averaging 1.43m
"""
In [76]: def a():
    ...:     yield 1
    ...:     return 1
    ...:

In [80]: aa = a()

In [81]: getgeneratorstate(aa)
Out[81]: 'GEN_CREATED'

In [82]: next(aa)
Out[82]: 1

In [83]: getgeneratorstate(aa)
Out[83]: 'GEN_SUSPENDED'

In [84]: b = aa.send(222)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-84-b5a7980ee106> in <module>
----> 1 b = aa.send(222)

StopIteration: 1

In [85]: getgeneratorstate(aa)
Out[85]: 'GEN_CLOSED'

In [86]: b

In [87]: c = aa.send(33)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-87-d2f9f10a5b00> in <module>
----> 1 c = aa.send(33)

StopIteration:

In [88]: c
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-88-2b66fd261ee5> in <module>
----> 1 c

NameError: name 'c' is not defined

In [104]: c = b()

In [105]: c.send(111)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-105-ffa2730868ca> in <module>
----> 1 c.send(111)

TypeError: can't send non-None value to a just-started generator

In [97]: def b():
    ...:     while 1:
    ...:         yield from a()
    ...:

In [98]: c = b()

In [99]: next(c)
Out[99]: 1

In [100]: c.send(1)
Out[100]: 1

In [101]: c.send(1)
Out[101]: 1

In [102]: c.send(1)
Out[102]: 1

In [103]: c.send(1)
Out[103]: 1

why???

In [127]: def a():
     ...:     print('yield do')
     ...:     yield 1  # 可迭代对象的值的组成
     ...:     print('return do')
     ...:     return 2  # 返回函数执行结果

# 此时,a 只是一个函数,因为它显然要执行 return 
# yield from 要先把 a() 变为可迭代对象,即 [1]
# 所以,委派生成器 不断的循环 执行 a,即 取出 [1] 中的 1,这个 1 是 yield 的 1
In [128]: list(a())
yield do
return do
Out[128]: [1]

In [129]: b = list(a())
yield do
return do

In [130]: b
Out[130]: [1]
In [106]: def a():
     ...:     print('yield do')
     ...:     yield 1
     ...:     print('return do')
     ...:     return 2

In [118]: d = a()

In [119]: next(a)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-119-15841f3f11d4> in <module>
----> 1 next(a)

TypeError: 'function' object is not an iterator

In [120]: def a():
     ...:     print('yield do')
     ...:     yield 1
     ...:     print('return do')
     ...:     #return 2

In [121]: d = a()

In [122]: next(d)
yield do
Out[122]: 1

In [123]: d.send(2)
return do
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-123-aca2617ea1f4> in <module>
----> 1 d.send(2)

StopIteration:

In [124]: def a():
     ...:     print('yield do')
     ...:     x = yield 1
     ...:     print('return do')
     ...:     if x is None:
     ...:         return 2

In [125]: d = a()

In [126]: next(d)
yield do
Out[126]: 1

外层 for 循环每次迭代会新建一个 grouper 实例,赋值给 group 变量;group 是委派生成器。调用 next(group),预激委派生成器 grouper,此时进入 while True 循环,调用子生成器 averager 后,在 yield from 表达式处暂停。

内层 for 循环调用 group.send(value),直接把值传给子生成器 averager。同时,当前的 grouper 实例(group)在 yield from 表达式处暂停。

内层循环结束后,group 实例依旧在 yield from 表达式处暂停,因此,grouper函数定义体中为 results[key] 赋值的语句还没有执行。如果外层 for 循环的末尾没有 group.send(None),那么 averager 子生成器永远不会终止,委派生成器 group 永远不会再次激活,因此永远不会为 results[key]赋值。

外层 for 循环重新迭代时会新建一个 grouper 实例,然后绑定到 group 变量上。前一个 grouper 实例(以及它创建的尚未终止的 averager 子生成器实例)被垃圾回收程序回收。

 这个试验想表明的关键一点是,如果子生成器不终止,委派生成器会在 yield from 表达式处永远暂停。如果是这样,程序不会向前执行,因为 yield from(与 yield 一样)把控制权转交给客户代码(即,委派生成器的调用方)了。显然,肯定有任务无法完成。

因为委派生成器相当于管道,所以可以把任意数量个委派生成器连接在一起:一个委派生成器使用 yield from 调用一个子生成器,而那个子生成器本身也是委派生成器,使用 yield from 调用另一个子生成器,以此类推。最终,这个链条要以一个只使用 yield表达式的简单生成器结束;不过,也能以任何可迭代的对象结束

任何 yield from 链条都必须由客户驱动,在最外层委派生成器上调用 next(...) 函数或 .send(...) 方法。可以隐式调用,例如使用 for 循环。

yield from 的意义

“把迭代器当作生成器使用,相当于把子生成器的定义体内联在 yield from 表达式中。此外,子生成器可以执行 return 语句,返回一个值,而返回的值会成为 yield from 表达式的值。”

PEP 380 中已经没有这段宽慰人心的话,因为没有涵盖所有极端情况。不过,一开始可以这样粗略地说。

批准后的 PEP 380 在“Proposal”一节(https://www.python.org/dev/peps/pep-0380/#proposal)分六点说明了 yield from 的行为。这里,我几乎原封不动地引述,不过把有歧义的“迭代器”一词都换成了“子生成器”,还做了进一步说明。示例 16-17 阐明了下述四点。

1 子生成器产出的值都直接传给委派生成器的调用方(即客户端代码)。

2 使用 send() 方法发给委派生成器的值都直接传给子生成器。如果发送的值是 None,那么会调用子生成器的__next__() 方法。如果发送的值不是 None,那么会调用子生成器的 send() 方法。如果调用的方法抛出 StopIteration 异常,那么委派生成器恢复运行。任何其他异常都会向上冒泡,传给委派生成器。

3 生成器退出时,生成器(或子生成器)中的 return expr 表达式会触发 StopIteration(expr) 异常抛出。

4 yield from 表达式的值是子生成器终止时传给 StopIteration 异常的第一个参数。

yield from 结构的另外两个特性与异常和终止有关。

1 传入委派生成器的异常,除了 GeneratorExit 之外都传给子生成器的 throw() 方法。如果调用 throw() 方法时抛出 StopIteration 异常,委派生成器恢复运行。StopIteration 之外的异常会向上冒泡,传给委派生成器。

2 如果把 GeneratorExit 异常传入委派生成器,或者在委派生成器上调用 close() 方法,那么在子生成器上调用 close() 方法,如果它有的话。如果调用 close() 方法导致异常抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出 GeneratorExit 异常。

RESULT = yield from EXPR
_i = iter(EXPR)
try:
    _y = next(_i)
except StopIteration as _e:
     _r = _e.value
else:
     while 1:
        _s = yield _y
     try:
        _y = _i.send(_s)
     except StopIteration as _e:
        _r = _e.value
        break
RESULT = _r

'''
_i(迭代器)
  子生成器
_y(产出的值)
  子生成器产出的值
_r(结果)
  最终的结果(即子生成器运行结束后 yield from 表达式的值)
_s(发送的值)
  调用方发给委派生成器的值,这个值会转发给子生成器
_e(异常)
  异常对象(在这段简化的伪代码中始终是 StopIteration 实例)
'''
_i = iter(EXPR) ➊
try:
    _y = next(_i) ➋
except StopIteration as _e:
    _r = _e.value ➌
else:
    while 1: ➍
        try:
            _s = yield _y ➎
        except GeneratorExit as _e: ➏
            try:
                 _m = _i.close
             except AttributeError:
                 pass
             else:
                _m()
                 raise _e
         except BaseException as _e: ➐
             _x = sys.exc_info()
             try:
                 _m = _i.throw
             except AttributeError:
                 raise _e
             else: ➑
                 try:
                     _y = _m(*_x)
                 except StopIteration as _e:
                     _r = _e.value
                     break
         else: ➒
             try: ➓
                 if _s is None: ⓫
                     _y = next(_i)
                 else:
                     _y = _i.send(_s)
             except StopIteration as _e: ⓬
                 _r = _e.value
                 break
RESULT = _r ⓭

使用案例 : 使用协程做离散事件仿真

离散事件仿真是一种把系统建模成一系列事件的仿真类型。

In [1]: from collections import namedtuple

In [2]: E = namedtuple('E','time proc action')

In [3]: def taxi_process(i,n,start_time):  # 出租车编号,载客次数,出车时间
   ...:     time = yield E(start_time, i, 'leave garage')  # 出租车出车
   ...:     for k in range(n):
   ...:         time = yield E(time,i,'pick up passenger')  # 乘客上车
   ...:         time = yield E(time,i,'drop off passenger')  # 乘客下车
   ...:     yield E(time,i,'going home')
   ...:

In [4]: t = taxi_process('xmen',3,8)

In [5]: next(t)
Out[5]: E(time=8, proc='xmen', action='leave garage')

In [6]: _
Out[6]: E(time=8, proc='xmen', action='leave garage')

In [7]: t.send(_.time+10)
Out[7]: E(time=18, proc='xmen', action='pick up passenger')

In [8]: t.send(_.time+1)
Out[8]: E(time=19, proc='xmen', action='drop off passenger')

In [9]: t.send(_.time+10)
Out[9]: E(time=29, proc='xmen', action='pick up passenger')

In [10]: t.send(_.time+1)
Out[10]: E(time=30, proc='xmen', action='drop off passenger')

In [11]: t.send(_.time+10)
Out[11]: E(time=40, proc='xmen', action='pick up passenger')

In [12]: t.send(_.time+1)
Out[12]: E(time=41, proc='xmen', action='drop off passenger')

In [13]: t.send(_.time+10)
Out[13]: E(time=51, proc='xmen', action='going home')

In [14]: t.send(_.time+1)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-14-f9e5703fbef3> in <module>
----> 1 t.send(_.time+1)

StopIteration:

在这个仿真系统中,各个出租车协程由 Simulator.run 方法中的主循环驱动。仿真“钟”保存在 sim_time 变量中,每次产出事件时都会更新仿真钟。

构造 taxis 字典

taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL)
             for i in range(num_taxis)}
sim = Simulator(taxis)

构造结果

taxis = {0: taxi_process(ident=0, trips=2, start_time=0),
         1: taxi_process(ident=1, trips=4, start_time=5),
         2: taxi_process(ident=2, trips=6, start_time=10)}
sim = Simulator(taxis)

main 函数

sim = Simulator(taxis)
sim.run(end_time)

出租车仿真类

class Simulator:
    def __init__(self, procs_map):
        self.events = queue.PriorityQueue()
        self.procs = dict(procs_map)
    def run(self, end_time):
        # 排定各辆出租车的第一个事件
        for _, proc in sorted(self.procs.items()):
            first_event = next(proc)
            self.events.put(first_event)
        # 这个仿真系统的主循环
        sim_time = 0
        while sim_time < end_time:
            if self.events.empty():
                print('*** end of events ***')
                break
            current_event = self.events.get()
            sim_time, proc_id, previous_action = current_event
            print('taxi:', proc_id, proc_id * ' ', current_event)
            active_proc = self.procs[proc_id]
            next_time = sim_time + compute_duration(previous_action)
            try:
                next_event = active_proc.send(next_time)
            except StopIteration:
                del self.procs[proc_id]
            else:
                self.events.put(next_event)
        else:
            msg = '*** end of simulation time: {} events pending ***'
            print(msg.format(self.events.qsize()))

这个示例的要旨是说明如何在一个主循环中处理事件,以及如何通过发送数据驱动协程。这是 asyncio 包底层的基本思想。

原文地址:https://www.cnblogs.com/pythonwl/p/15793642.html