python学习笔记七——协程

7.协程

7.1把生成器当作协程

​ python2.2引入yield关键字实现了的生成器函数,python2.5中为生成器对象添加了额外的方法和功能,其中最值得关注的是.send()方法。与__next__()方法一样,.send()方法致使生成器前进到下一个yield语句。不过.send()方法还允许使用生成器的客户把数据发给自己,即不管传给.send()方法什么参数,那个参数都会成为生成器函数定义体中对应的yield表达式的值。也就是说,.send()方法允许客户代码和生成器之间双向交换数据。而__next__()方法只允许客户从生成器中获取数据。

​ 这项重要的“改进”甚至改变了生成器的本性:像这样使用的话,生成器就变身成为协程,两者之间的需要注意一下几点:

  • 生成器用于生成供迭代的数据

  • 协程是数据的消费者

  • 为了避免脑袋炸裂,不能把两个概念混为一谈

  • 协程与迭代无关

  • 注意,虽然在协程中会使用yield产出值,但这与迭代无关

7.2 yield关键词在协程中的作用

​ python中,yield关键字有两种释义:产出和让步。对于python生成器中的yield来说,这两个含义都成立。yield item这行代码会产出一个值,提供给next(...)的调用方;此外,还会做出让步,暂停执行生成器,让调用方继续工作,知道需要使用另一个值时再调用next()。调用方会从生成器中拉取值。

​ 协程和生成器对yield关键字的使用方法不同,协程中yield通常出现在表达式的右边 (例如,datum = yield),可以产出值,也可以不产出——如果yield关键字后面没有表达式,那么生成器产出None。协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的方法是.send(datum)方法,而不是next(...)函数。通常,调用方会把值推送给协程。

#使用协程实现记忆性计算平均值的函数
def average():
    total = 0.0
    count = 0
    average = None
    while True:
        msg = ''
        try:
            term = yield average
        except ZeroDivisionError:
            msg = 'Please enter number'
        else:
            total += term
            count += 1
            average = total/count

7.3 yiled from句法

7.3.1 生成器中的yield from语句

#yield from可以简化for循环中的yield表达式,例如:
def gen():
    for c in 'AB':
        yield c
    for i in range(1,3):
        yield i
#可以改写为:
def gen():
    yield from 'AB'
    yield from range(1,3)
    
    
#使用yield from链接可迭代的对象
def chain(*iterables):
    for it in iterables:
        yield from it      

7.3.2 协程中的 yield from句法

​ yield from:把职责委托给子生成器的句法,使用它可以把复杂的生成器重构成小型的嵌套生成器,省去了之前把生产器的工作委托给子生成器所需的大量样本代码。在协程中,yield from的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。以下是在协程中使用yield from语句需要了解的新的术语:

  • 委派生成器

    • 包含yield from<iterable>表达式的生成器函数,委派生成器在yield from表达式处暂停时,调用方可以直接把数据发给子生成器,子生成器再把产出的值发给调用法。子生成器返回之后,解释器会抛出StopIteration异常,并把返回值附加在异常对象上,此时委派生成器会恢复

  • 子生成器:

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

  • 调用方

    • 委派生成器的客户端代码

from collections import namedtuple
​
Result = namedtuple('Result','count average')
​
#子生成器
def averager():
​
    average = None
    total = 0.0
    count = 0
    while True:
        item = yield
        if item is None:
            break
        total += item
        count += 1
        average = total/count
    return Result(count,average)
​
​
​
#委派生成器
```
grouper发送的每个值都会经yield from处理,通过管道传给averager实例。grouper会在yield from表达式处暂停,等待averager实例处理客户端发来的值。averager实例运行完毕后,返回的值绑定到results[key]上。while循环会不断创建averager实例,处理更多的值
```
def grouper(results,key):
    while True:
        results[key] = yield from averager()
​
#调用方
def main(data):
    results = {}
    for key,values in data.items():
        group = grouper(results,key)
        next(group)
        for value in values:
            group.send(value)
        group.send(None)
    print(results)
​
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
            result.count,group,result.average,unit
        ))
​
data = {
    'girls;kg':
        [40.9,38.5,44.3,42.2,45.2,41.7,44.5,38,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)

7.4 协程中异常的处理

协程中未处理的异常会向上冒泡,传给next函数或者send方法的调用法(即触发协程的对象),终止协程的一种方式是:发送某个哨符值,让协程退出。内置的None常量经常用作哨符值。从python2.5开始,客户代码可以在生成器对象上调用两个方法,显式地把异常发给协程。

  • geneator.throw(exc_type[, exc_value[, trackback]])

    • 致使生成器在暂停的yield表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,而产出的值会成为调用generator.throw方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中

  • generator.close()

    • 致使生成器在暂停的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或者抛出了StopIteration异常(通常指运行到结尾),调用方不会不错。如果收到GeneratorExit,生成器一定不能产出值,否则解释器抛出RuntimeError异常。生成器抛出的其他异常会向上冒泡,传给调用方。另外如果无法处理传入的异常,协程会停止,即状态变成‘GEN_CLOSED'。

7.4.1 yield from句法结构对异常的处理

​ yield from结构会在内部自动捕获stopIteration异常。这种处理方式与for循环处理StopIteration异常的方式一样:循环机制使用使用易于理解的方式处理异常。对于yield from结构来说,解释器不仅会捕获StopIteration异常,还会把value属性的值变为yield from表达式的值。具体来说:

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

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

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

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

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

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

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

猜你喜欢

转载自blog.csdn.net/jasonzhoujx/article/details/81506303
今日推荐