larken

勤奋的人生才有价值

导航

第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 结构驱动的生成器函数。

 

posted on 2018-08-29 22:21  larken  阅读(136)  评论(0编辑  收藏  举报