zoukankan      html  css  js  c++  java
  • 《Fluent Python》 CH.16_控制流程_协程(进化的yield生成器、yield from简化iter或者委派器、next激活、close关闭、throw传递异常)

    小结

    • 共计 61页
      • 字典为动词“to yield”给出了两个释义:产出和让步。对于 Python 生成器 中的 yield 来说,这两个含义都成立。
    • 协程与生成器类似,都是定义体中包含 yield 关键字的 函数。可是,在协程中,yield 通常出现在表达式的右边(例 如,datum = yield),可以产出值,也可以不产出——如果 yield 关键字后面没有表达式,那么生成器产出 None。
    • 协程可能会从调用方 接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方 法,而不是 next(...) 函数。通常,调用方会把值推送给协程。
    • yield 都是一种流程控制工具,使用它可以实现协作式多任务:协 程可以把控制器让 步给中心调度程序,从而激活其他的协程。

    本章 涵盖以下话题:

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

    本章速读

    补充知识点

    • 协程的意义:协程是在单线程中的多个任务同时执行的相互配合,避免了线程之间切换造成的资源浪费
    • 协程的适用性: 对于I/O密集型的操作,可以采用协程方式来做,有效降低线程的数量和资源的损耗。如果存在计算密集型(占用cpu的操作很高),不要使用协程,最好使用多线程。
    • 协程的实现:一个调度器,负责记录字节码的挂起点,将挂起点存到栈中;最终节约的是重复的线程本体的固定内存大小。
    • 协程与线程的取舍
      • 节省资源,轻量,具体就是:节省内存,每个线程需要分配一段栈内存,以及内核里的一些资源节省分配线程的开销(创建和销毁线程要各做一次syscall)节省大量线程切换带来的开销与NIO配合实现非阻塞的编程,提高系统的吞吐使用起来更加舒服顺畅(async+await,跑起来是异步的,但写起来感觉上是同步的)
      • 改用netty、Vert.x等NIO或者BIO框架来处理
      • 作者:大宽宽
        链接:https://www.zhihu.com/question/332042250/answer/734115120

    16.1 生成器如何进化成协程

    • 生成器 API 中增加了 .send(value) 方法。生成器的调用方可以使用 .send(...) 方法发送数据,发送的数据会成为生成器函数中yield 表达式的值。因此,生成器可以作为协程使用。协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。
    • .send(...) 方法
    • .throw(...):前者的作用是让调用方抛出异常,在生成器中处理;
    • .close() 方法:后者的作用 是终止生成器。

    PEP 380 对生成器函数的句法做了两处改动,以便更好地作为 协程使用。

    • 现在,生成器可以返回一个值;以前,如果在生成器中给 return 语句提供值,会抛出 SyntaxError 异常。
    • 新引入了 yield from 句法,使用它可以把复杂的生成器重构成小 型的嵌套生成器,省去了之前把生成器的工作委托给子生成器所需 的大量样板代码。

    16.2 用作协程的生成器的基本行为

    def simple_coroutine():
        print('-> coroutine started')
        x = yield
        print('-> coroutine started:', x)
    
    my_coro = simple_coroutine()
    my_coro
    
    <generator object simple_coroutine at 0x0000024B090CBF90>
    
    next(my_coro)
    
    -> coroutine started
    
    my_coro.send(2333)
    
    
    -> coroutine started: 2333
    
    
    
    ---------------------------------------------------------------------------
    
    StopIteration                             Traceback (most recent call last)
    
    <ipython-input-5-4924dd66e4fb> in <module>
    ----> 1 my_coro.send(2333)
          2 
          3 
    
    
    StopIteration: 
    

    这里,控制权流动到协程定义体的末尾,导致生成器像往常一样抛 出 StopIteration 异常。

    协程可以身处四个状态中的一个

    当前状态可以使用 inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字 符串中的一个。

    只有在多线程应用中才能看到这个状态。此外,生成器对象在自己身上调用 getgeneratorstate 函数也行,不过这样做没什么用。

    • 'GEN_CREATED' 等待开始执行。
    • 'GEN_RUNNING' 解释器正在执行。
    • 'GEN_SUSPENDED' 在 yield 表达式处暂停。 'GEN_CLOSED' 执行结束。
    my_coro = simple_coroutine()
    my_coro.send(1729)
    
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-6-b4cdcef3e729> in <module>
          1 my_coro = simple_coroutine()
    ----> 2 my_coro.send(1729)
          3 
          4 
    
    
    TypeError: can't send non-None value to a just-started generator
    

    以上,还没有使用next进行激活这个生成器。

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

    举个产出多个值的协程例子

    def simple_coro2(a):
        print('-> Started: a=', a)
        b = yield a
        print('-> Started: b=', b)
        c = yield a + b
        print('-> Started: c=', c)
    
    
    my_coro2 = simple_coro2(12)
    
    next(my_coro2)
    
    -> Started: a= 12
    
    
    
    
    
    12
    
    from inspect import getgeneratorstate
    getgeneratorstate(my_coro2)
    
    
    'GEN_SUSPENDED'
    
    my_coro2.send(2)
    
    
    -> Started: b= 2
    
    
    
    
    
    14
    
    my_coro2.send(3)
    
    
    -> Started: c= 3
    
    
    
    ---------------------------------------------------------------------------
    
    StopIteration                             Traceback (most recent call last)
    
    <ipython-input-16-df2b2335164d> in <module>
    ----> 1 my_coro2.send(3)
          2 
          3 
    
    
    StopIteration: 
    
    getgeneratorstate(my_coro2)
    
    
    'GEN_CLOSED'
    

    16.3 示例:使用协程计算移动平均值

    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
            term = yield average # 这里的 yield 表达式用于暂停执行协程,把结果发给调用方;还用 于接收调用方后面发给协程的值,恢复无限循环。
            total += term
            count += 1
            average = total/count
    print('这个无限循环表明,只要调用方不断把值发给这个协程,它就会一 直接收值,然后生成结果。仅当调用方在协程上调用 .close() 方法, 或者没有对协程的引用而被垃圾回收程序回收时,这个协程才会终止。')
    
    这个无限循环表明,只要调用方不断把值发给这个协程,它就会一 直接收值,然后生成结果。仅当调用方在协程上调用 .close() 方法, 或者没有对协程的引用而被垃圾回收程序回收时,这个协程才会终止。
    
    avg = averager()
    next(avg)
    
    avg.send(233)
    
    
    233.0
    
    avg.send(2332)
    
    
    1282.5
    
    avg.close()
    
    
    avg.send(233)
    
    
    ---------------------------------------------------------------------------
    
    StopIteration                             Traceback (most recent call last)
    
    <ipython-input-23-8024ab53a307> in <module>
    ----> 1 avg.send(233)
          2 
          3 
    
    
    StopIteration: 
    

    16.4 预激协程的装饰器

    示例 16-5 coroutil.py:预激协程的装饰器

    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
    
    
    @coroutine
    def averager2():
        total = 0.0
        count = 0
        average = None
        while True:
            term = yield average # 这里的 yield 表达式用于暂停执行协程,把结果发给调用方;还用 于接收调用方后面发给协程的值,恢复无限循环。
            total += term
            count += 1
            average = total/count
    
    coro_avg2 = averager2()
    coro_avg2.send(233)
    
    233.0
    
    coro_avg2.send(2332)
    
    
    1282.5
    

    16.5 终止协程和异常处理 -- 协程终止并返还给调用方

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

    示例 16-7 举例说明如何使用示例 16-6 中由 装饰器定义的 averager 协程。

    示例 16-7 未处理的异常会导致协程终止

    coro_avg2.send('aaa')
    
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-31-7a6827193227> in <module>
    ----> 1 coro_avg2.send('aaa')
          2 
          3 
    
    
    <ipython-input-26-86704c23b5b3> in averager2()
          6     while True:
          7         term = yield average # 这里的 yield 表达式用于暂停执行协程,把结果发给调用方;还用 于接收调用方后面发给协程的值,恢复无限循环。
    ----> 8         total += term
          9         count += 1
         10         average = total/count
    
    
    TypeError: unsupported operand type(s) for +=: 'float' and 'str'
    
    coro_avg2.send(2)
    
    
    ---------------------------------------------------------------------------
    
    StopIteration                             Traceback (most recent call last)
    
    <ipython-input-32-833d84c532c6> in <module>
    ----> 1 coro_avg2.send(2)
          2 
          3 
    
    
    StopIteration: 
    
    • generator.throw()

      • 致使生成器在暂停的 yield 表达式处抛出指定的异常。如果生成 器处理了抛出的异常,代码会向前执行到下一个 yield 表达式,而产 出的值会成为调用 generator.throw 方法得到的返回值。
      • 如果生成器 没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。
      • 如果传入协程的异常没有处理,协程会停止,即状态变成 'GEN_CLOSED'。
    exc_coro = demo_exc_handling()
     exc_coro.throw(DemoException)
    
    • generator.close()

    致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常。 如果生成器没有处理这个异常,或者抛出了 StopIteration 异常(通 常是指运行到结尾),调用方不会报错。如果收到 GeneratorExit 异 常,生成器一定不能产出值,否则解释器会抛出 RuntimeError 异常。 生成器抛出的其他异常会向上冒泡,传给调用方。

    16.6 让协程返回值

    • yield不返回值
    • 在while循环中做约定,可以结束掉循环的条件
    • 在生成器函数结尾处加上return

    16.7 使用yield from

    yield from 可用于简化 for 循环中的 yield 表达式。

    def gen():
        for c in 'AB':
            yield c
        for i in range(1,3):
            yield i
    list(gen())
    
    ['A', 'B', 1, 2]
    

    可以进行简化

    def gen_simple():
        yield from 'AB'
        yield from range(1,3)
    gen_simple()
    
    <generator object gen_simple at 0x0000024B090044A0>
    
    list(gen_simple())
    
    ['A', 'B', 1, 2]
    

    示例 16-16 使用 yield from 链接可迭代的对象

    同上

    原理: 先iter()解析一次!

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

    yield from 结构的 本质作用无法通过简单的可迭代对象说明,而要发散思维,使用嵌套的 生成器。因此,引入 yield from 结构的 PEP 380 才起了“Syntax for Delegating to a Subgenerator”(“把职责委托给子生成器的句法”)这个标 题。

    yield from 职责2: 把职责委托给子生成器的句法

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

    委派生成器

    包含 yield from 表达式的生成器函数。

    子生成器

    从yield from 表达式中 部分获取的生成器。这就是 PEP 380 的标题(“Syntax for Delegating to a Subgenerator”)中所说的“子生成器”(subgenerator)。

    实现上述职责的示例

    [?.png 图 16-2:委派生成器在 yield from 表达式处暂停时,调用方可以直 接把数据发给子生成器,子生成器再把产出的值发给调用方。子生成器返回之后,解释器会抛出 StopIteration 异常,并把返回值附 加到异常对象上,此时委派生成器会恢复]

    #### 2) 委派生成器 grouper
    
    # 委派生成器
    def grouper(results, key):
        while True:
            # 计算传入的key对应的所有值的平均值
            results[key] = yield from averager()
    
    
    #### 3)子生成器averager
    
    from collections import namedtuple
    Result = namedtuple('Result', 'count average')
    # 子生成器
    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
            term = yield
            if term is None:
                break
            total += term
            count += 1
            average = total/count
            print(Result(count, average))
        return Result(count, average)
    
    
    
    #### 1) 客户端代码,调用方main
    
    # 输出报告
    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))
    
    
    # 客户端代码,即调用方
    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)
    
    data = {'a;kg': [1,2,3], 'b;cm': [4,5,6], 'c;m': [7,8,9]}
    main(data)
    
    Result(count=1, average=1.0)
    Result(count=2, average=1.5)
    Result(count=3, average=2.0)
    {'a;kg': Result(count=3, average=2.0)}
    Result(count=1, average=4.0)
    Result(count=2, average=4.5)
    Result(count=3, average=5.0)
    {'a;kg': Result(count=3, average=2.0), 'b;cm': Result(count=3, average=5.0)}
    Result(count=1, average=7.0)
    Result(count=2, average=7.5)
    Result(count=3, average=8.0)
    {'a;kg': Result(count=3, average=2.0), 'b;cm': Result(count=3, average=5.0), 'c;m': Result(count=3, average=8.0)}
    

    16.8 yield from的意义

    把迭代器当作生成器使用,相当于把子生成器的定义体内联在 yield from 表达式中。

    此外,子生成器可以执行 return 语句, 返回一个值,而返回的值会成为 yield from 表达式的值。

    16.9 使用案例:使用协程做离散事件仿真

    你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
  • 相关阅读:
    2015-01-27-从实验出发理解buffer与cache区别-吴伟顺
    【实习记】2014-09-26恢复linux下误删的ntfs盘中的文件
    【实习记】2014-09-24万事达卡bin查询项目总结
    【实习记】2014-09-04浏览代码查middle资料+总结我折腾过的源码浏览器
    【实习记】2014-09-01从复杂到简单:一行命令区间查重+长整型在awk中的bug
    【实习记】2014-08-29算法学习Boyer-Moore和最长公共子串(LCS)
    【实习记】2014-08-28知值求范围问题
    【实习记】2014-08-27堆排序理解总结+使用typedef指代函数指针
    【实习记】2014-08-26都是回车惹的祸——shell脚本必须是unix行尾
    【实习记】2014-08-24实习生无法映射磁盘替代方案rsync+非默认端口22设置
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/14479768.html
Copyright © 2011-2022 走看看