zoukankan      html  css  js  c++  java
  • 流畅的python第十六章协程学习记录

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

    生成器作为协程使用时的行为和状态
    使用装饰器自动预激协程
    调用方如何使用生成器对象的 .close() 和 .throw(...) 方法控制协程
    协程终止时如何返回值
    yield from 新句法的用途和语义
    使用案例——使用协程管理仿真系统中的并发活动

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

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

    因为 send 方法的参数会成为暂停的 yield 表达式的值,所以,仅当协程处于暂停状态时
    才能调用 send 方法,例如 my_coro.send(42)。不过,如果协程还没激活(即,状态是
    'GEN_CREATED'),情况就不同了。因此,始终要调用 next(my_coro) 激活协程——也
    可以调用 my_coro.send(None),效果一样。

    在创建好协程之后,需要调用next激活协程,否则会报错。

    最先调用 next(my_coro) 函数这一步通常称为“预激”(prime)协程(即,让协程向前执
    行到第一个 yield 表达式,准备好作为活跃的协程使用)。

    预激协程的装饰器

    由于使用协程之前,必须要激活协程,为了简化协程用法,有时会使用一个预激装饰器

    from functools import wraps
    def coroutine(func):
    """装饰器:向前执行到第一个`yield`表达式,预激`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 异常。生成器抛出的其他异常会向上冒泡,传给调用方。

    如何使用close和throw

    class DemoException(Exception):
    """为这次演示定义的异常类型。"""
    def demo_exc_handling():
        print('-> coroutine started')
        while True:
            try:
                x = yield
            except DemoException: ➊
                print('*** DemoException handled. Continuing...')
            else: ➋
                print('-> coroutine received: {!r}'.format(x))
        raise RuntimeError('This line should never run.')

    获取协程的返回值虽然要绕个圈子,但这是 PEP 380 定义的方式,当我们意识到这一点之
    后就说得通了:yield from 结构会在内部自动捕获 StopIteration 异常。这种处理方
    式与 for 循环处理 StopIteration 异常的方式一样:循环机制使用用户易于理解的方式
    处理异常。对 yield from 结构来说,解释器不仅会捕获 StopIteration 异常,还会把
    value 属性的值变成 yield from 表达式的值。可惜,我们无法在控制台中使用交互的方
    式测试这种行为,因为在函数外部使用 yield from(以及 yield)会导致句法出错。

    使用yield from

    for c in 'ab':
        yield c
    等价于
    yield from 'ab'

    yield from x 表达式对 x 对象所做的第一件事是,调用 iter(x),从中获取迭代器。因
    此,x 可以是任何可迭代的对象。

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

    yield from的意义

  • 相关阅读:
    Github 上 36 个最实用的 Vue 开源库
    C 语言快速入门,21 个小项目足矣!「不走弯路就是捷径」
    18个挑战项目带你快速入门深度学习
    Linux 运维入门到跑路书单推荐
    Python 网络爬虫的常用库汇总
    45 个常用Linux 命令,让你轻松玩转Linux!
    [新手必备]Python 基础入门必学知识点笔记
    快速入门 Python 数据分析实用指南
    18位不重复订单号
    相对路径转绝对路径
  • 原文地址:https://www.cnblogs.com/lgh344902118/p/8392170.html
Copyright © 2011-2022 走看看