第16章 协程
#《流流畅的Python》第16章 协程 #16.1 生成器如何进化成协程 #16.2 用作协程的生成器的基本行为 def simple_coroutine(): print('->coroutine started') x=yield print('->coroutine received:',x) my_coro=simple_coroutine() my_coro next(my_coro) my_coro.send(42) #最先调用next(my_coro)函数这一步通常称为“预激”(prime)协程(即,让协程向前执行到第一个yield表达式,准备好作为活跃的协程使用)。 from inspect import getgeneratorstate def simple_core2(a): print('->Started:a',a) b=yield a print('->Started:b',b) c=yield a+b print('->Started:c',c) my_coro2=simple_core2(14) print(getgeneratorstate(my_coro2)) print(next(my_coro2)) print(getgeneratorstate(my_coro2)) print(my_coro2.send(28)) my_coro2.send(99) print(getgeneratorstate(my_coro2)) #16.3 示例:使用协程计算移动平均值 #示例16-3 coroaverager0.py:定义一个计算移动平均值的协程 def average(): total=0.0 count=0 average=None #average变量的初始值—None,因此不会出现在控制台中。 while True: term=yield average total+=term count+=1 average=total/count coro_avg=average() next(coro_avg) print(coro_avg.send(10)) print(coro_avg.send(30)) print(coro_avg.send(5)) #使用协程之前必须预激,可是这一步容易忘记。为了避免忘记,可以在协程上使用一个特殊的装饰器。 #16.4 预激协程的装饰器 #如果不预激,那么协程没什么用。调用 my_coro.send(x) 之前,记住一定要调用 next(my_coro)。为了简化协程的用法,有时会使用一个预激装饰器。 from functools import wraps def coroutine(func): @wraps(func) def primer(*args,**kwargs): gen = func(*args,**kwargs) next(gen) return gen return primer #16.5 终止协程和异常处理 #协程中未处理的异常会向上冒泡,传给next函数或send方法的调用方(即触发协程的对象)。 from coroaverager1 import averager coro_avg=average() print(coro_avg.send(40)) #使用@coroutine装饰器装饰的averager协程,可以立即开始发送值。 print(coro_avg.send(50)) print(coro_avg.send("spam")) #出错的原因是,发送给协程的'spam'值不能加到total变量上 print(coro_avg.send(60)) #示例16-8 coro_exc_demo.py:学习在协程中处理异常的测试代码 class DemoException(Exception): """为这次演示定义的异常类型。""" def demo_exc_handling(): print('-> coroutine started') while True: try: x=yield except DemoException: #特别处理DemoException异常。 print('*** DemoException handle. Continuing...') else: #如果没有异常,那么显示接收到的值。 print('-> coroutine received:{!r}'.format(x)) raise RuntimeError('This line should never run.') #这一行永远不会执行。 #示例16-9 激活和关闭demo_exc_handling,没有异常 exc_coro=demo_exc_handling() next(exc_coro) exc_coro.send(11) exc_coro.send(22) exc_coro.close() from inspect import getgeneratorstate print(getgeneratorstate(exc_coro)) #16.6 让协程返回值 from collections import namedtuple Result=namedtuple('Result','count average') def aveager(): total=0.0 count=0 average=None while True: term=yield if term is None: break total+=term count+=1 average=total/count return Result(count,average) coro_avg=aveager() next(coro_avg) coro_avg.send(10) coro_avg.send(30) coro_avg.send(6.5) try: coro_avg.send(None) except StopIteration as exc: result=exc.value print(result) #16.7 使用yield from def chain(*iterables): for it in iterables: yield from it s='ABC' t=tuple(range(3)) print(list(chain(s,t))) #16.8 yield from的意义 #16.9 使用案例:使用协程做离散事件仿真 def taxi_process(ident,trips,start_time=0): """每次改变状态时创建事件,把控制权让给仿真器""" time=yield Event(start_time,ident,'leave garage') for i in range(trips): time=yield Event(time,ident,'pick up passenger') time=yield Event(time,ident,'drop up passenger') yield Event(time,ident,'going home') #出租车进程结束 #16.10 本章小结 #计算移动平均值的示例展示了协程的常见用途:累加器,处理接收到的值。我们知道,可以在协程上应用装饰器,预激协程;在某些情况下,这么做更方便。 #最后要说明一点,本章对协程的定义是宽泛的、不正式的,即:通过客户调用 .send(...) 方法发送数据或使用 yield from 结构驱动的生成器函数。