十六、协程
生成器如何进化成协程
用作协程的生成器的基本行为
协程演示
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__
、send
、close
和 throw
方法的生成器。
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
包底层的基本思想。