• python 协程的学习记录


    协程是个子程序,执行过程中,内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行

    从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数。可是,在协
    程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可
    以不产出——如果 yield 关键字后面没有表达式,那么生成器产出 None。协程可能会从
    调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是
    next(...) 函数。通常,调用方会把值推送给协程。
    yield 关键字甚至还可以不接收或传出数据。不管数据如何流动,yield 都是一种流程控
    制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激
    活其他的协程。
    从根本上把 yield 视作控制流程的方式,这样就好理解协程了。

    使用协程要注意的地方是

    协程可以身处四个状态中的一个。当前状态可以使用
    inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字符串中的一个。
    'GEN_CREATED'
      等待开始执行。
    'GEN_RUNNING'
      解释器正在执行。

    'GEN_SUSPENDED'
      在 yield 表达式处暂停。
    'GEN_CLOSED'
      执行结束。

    协程创建之后,需要激活,需要调用next函数来激活协程。

    为了避免忘记,我们可以在协程上使用一个特殊的装饰器来帮助激活协程

    from functools import wraps
    
    def coroutine(func):
        @wraps(func)
        def primer(*args, **kwargs):
            gen = func(*args, **kwargs)
            next(gen)
            return gen
        return primer

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

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

    协程在遇到异常或者哨符值是会退出。

    协程在耗尽时会抛出StopIteration异常

    对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把
    value 属性的值变成 yield from 表达式的值。

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

    委派生成器
      包含 yield from <iterable> 表达式的生成器函数。
    子生成器
      从 yield from 表达式中 <iterable> 部分获取的生成器。
    调用方
      PEP 380 使用“调用方”这个术语指代调用委派生成器的客户端代码。在不同的语境
    中,我会使用“客户端”代替“调用方”,以此与委派生成器(也是调用方,因为它调用了子
    生成器)区分开。

    最后给个实例

    from collections import namedtuple
    
    Result = namedtuple('Result', 'count average')
    
    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
    #此处接受send发送的值,发送None则退出协程,协程处于上下文管理器中,所以数据会叠加,只有第一次激活协程的使用才会初始化total=0,count=0,average=None term
    = yield if term is None: break total += term count += 1 average = total / count return Result(count, average) 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) report(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.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)

    总结一下,使用协程的时,记得要激活协程,并且在协程耗尽时捕获异常,当然可以使用yield from来自动捕获,将协程看成是流程控制可能会好理解的多。

    大部分内容摘自流畅的python第十六章

  • 相关阅读:
    印象笔记和有道云笔记竞品分析
    印象笔记需求分析文档
    ES(Elasticsearch)
    java8中optional和.stream().map()
    设计模式-builder(构造器模式)
    throw与throws
    异常java.lang.NumberFormatException解决
    Spring注解
    Spring配置数据源以及hibernate
    log4j配置文件——hibernate
  • 原文地址:https://www.cnblogs.com/lgh344902118/p/8399189.html
Copyright © 2020-2023  润新知