zoukankan      html  css  js  c++  java
  • python中和生成器协程相关的yield from之最详最强解释,一看就懂(三)

    接上篇, 本节内容主要讲yield from的真正内在含义, yield from相关语法是Python3.3以后引入的, python官宣的解释是这样的

    RPE380增加了yield from表达式, 
    允许一个作为委托方的generator将自己部分操作委托给另一个generator(叫作sub-generator)。
    这样就可以让一段包含yield的代码被分散并安置到其它的sub-generator中。
    除此之外,sub-generator还可以返回一个值,并且这个值可以方便地被作为委托方的generator所获取

    这段话怎么理解?  yield from 提供了一种在caller和generator/subroutine之间的透明的双向通道从,即caller可以发送数据到底层corutine,也可以直接从底层的generator读取数据

    依然不好理解。我考虑了好几种方案,参阅了好多文章,都觉得说不清楚,所以我这里通过一个非常简单模拟系统来一步步详解说明

    一. 系统模型。

    假设我们有一个系统, 它的业务需求是这样:

    1. 需要读取一段放在一个常量列表中的文本, 每个item表示一行文本。

    2. 每读入一行,则先打印双小于号 "<<",然后打印读入的文本行

    3. 如果文本全部成功读取,则最后打印“done”表示应用正常结束

    二。 系统最初版本,由一个reader和app实现,转化的软件需求分别如下

    1. reader是一个generator。

      1)用来模拟文本读取,每次读取text中一行,并返回该行文本

           2)如果文本全部读完,返回‘done’

    2. app是主线业务, 软件需求如下:

         1)while循环中,每次通过next读取reader一个生成的值,并在文本前加“<<"后打印

         2)如果reader产生StopIteration异常, 则打印reader的返回值

    这个初版用python实现如下:

    # reader是一个生成器, 每次调用,它将读取列表中一个值并返回
    def reader(text):
        for line in text:
            yield line
        return 'done'
        
    # app是定义的一个简单应用,将reader读出的值打印出来
    def app(text):    
        try:
            r = reader(text)
            while True:
                line = next(r)
                print('<< %s' % line)
        except StopIteration as e:
                print(e.value)
                
    #启动app应用
    app(('a','b','c','d')) 

    执行 app(('a','b','c','d')) 得到如下结果

    << a
    << b
    << c
    << d
    done

    三。新需求引入

    现在系统需求改变了,在文件开始读取和结束读取之后,需要记录日志以满足运维需求,但系统原有业务无关,是和主线业务正交的非业务需求。为了避免以后再次修改app应用,引入一个代理proxyReader处理这些切面类需求。

    1)Reader只涉及底层文本读取,和之前一样。

    2)定义前后两个方法。 运维需求省略具体实现,并且抽象成一个before和after方法,里面可以添加记录日志等运维相关的需求

    def before():   # 文件开始处理之前的额外处理,比如记录日志
        pass  
        
    def after():    # 文件读取完成之后的额外处理,比如记录日志
        pass          

    3) app的改动:通过代理从reader获取值, 不再直接使用reader,这样所有和app无关的运维相关需求都可以在代理中实现, 而且app和reader均无再做任何修改,所有运维需求带来的改动以后都被将封装在代理中

    def app(text):    
        try:
            r = proxyReader(text)
            while True:
                line = next(r)
                print('<< %s' % line)
    except StopIteration as e:
                print(e.value)

    4)代理生成器的软件需求。

         3-1)不能对reader的产生的值做任何修改,原样上报给app

         3-2) 需要处理新加的非主线的运维需求

    def proxyReader(text):
        before()      #开始读之前运维需求处理
        r = reader(text)
        while True:
            line = next(r)
            yield '<< %s' % line
        after()       #结束读之后运维需求处理

    5)完整代码如下

    def before():   # 文件开始处理之前的额外处理
        pass  
        
    def after():    # 文件读取完成之后的额外处理
        pass          
    
    # reader是一个生成器, 每次调用,它将读取列表中一个值并返回
    def reader(text):
        for line in text:
            if line=='':
                raise EmptyException()
            else:
                yield line
        return 'done'
    
    def proxyReader(text):
        before()      #开始读之前运维需求处理
        r = reader(text)
        while True:
            line = next(r)
            yield '<< %s' % line
        after()       #结束读之后运维需求处理
        
    def app(text):    
        try:
            r = proxyReader(text)
            while True:
                line = next(r)
                print('<< %s' % line)
        except StopIteration as e:
                print(e.value)
    
                
    #启动app应用
    app(('a','b','c','d')) 
    View Code

    执行 app(('a','b','c','d')) 得到如下类似结果

    << << a
    << << b
    << << c
    << << d
    Traceback (most recent call last):
      File "yieldproxy.py", line 20, in proxyReader
        line = next(r)
    StopIteration: done
    
    The above exception was the direct cause of the following exception:
    
    Traceback (most recent call last):
      File "yieldproxy.py", line 35, in <module>
        app(('a','b','c','d'))
      File "yieldproxy.py", line 28, in app
        line = next(r)
    RuntimeError: generator raised StopIteration
    PS E:githubpython>

    四。 问题的引入

    结果不是我们所预期的, 出了两个问题:

    1. 每行文本都多打印了两个小于符号"<< "

    2. ‘done’虽然打印出来了,但是仍然产生一个StopIteration异常

    下面我们来分析这两个错误的原因,并解决。

    第一个错误,是我们的在编写proxyReader的时候,误读了需求,把主线业务需求(在每行前面新加”<< "当成了proxyReader的需求,结果导致重复打印)

    第二个错误,是由于proxyReader在使用reader的时候没有处理StopIteration, 而app处理的是proxyReader的StopIteration. 所以导致输出结果有异常日志

    这两个错误的根本原因其实是一个为了将包含yield的业务代码从app中分离出去, proxyReader采用的是用yield来转换subgenerator的数据到app,由于这个转换操作必须由proxyReader自己实现,所以它实际割裂了真正的reader和caller(也就是app)之间的数据上传通道。下面是基于yield的修改版本,正确实现数据转换和传送

    def proxyReader(text):
        before()      #开始读之前运维需求处理
        r = reader(text)
        try:
            while True:
                line = next(r)
                yield line                        # proxyReader不处理app的逻辑,只负责数据传递
        except StopIteration as e:
                return e.value                    # 需要处理reader产生的StopIteration, 并把reader的返回值原样返回
        after()       #结束读之后运维需求处理

    再次执行, 结果正确。

    五。python从语言级别的解决方案 -- yield from

    第四节中的错误,根本原因是proxyReader割裂了reader与app的联系,靠程序员人工保证很不靠谱, 所以这是python3.3引入yield from的真正动力所在, yield from的解决方案如下:

    def proxyReader(text):
        before()      #开始读之前运维需求处理
        yield from reader(text)
        after()       #结束读之后运维需求处理

    这是yield from真正牛逼的地方, 它使proxyReader无需在关心reader与app之间数据通道,这个数据通道被yield from完全封装,对proxyReader透明,而且proxyReader完全无权干涉, 也不可能在有马大哈式的重复打印, reader的输出是啥, yield from百分百保证了app收到的就是啥。除了代码简洁易懂,而且数据安全,牛逼不牛逼 !

    以上讲的是,yieldfrom如何搞定从底层的generator到顶层的caller(即app)的数据通道, 那么反过来,如果app要想底层的generator发送数据,是否也能安全透传喃。我们下回分解

    下一篇 : python中和生成器协程相关的yield from之最详最强解释,一看就懂(四)

    上一篇:  python中和生成器协程相关的yield from之最详最强解释,一看就懂(二)

  • 相关阅读:
    win7 64bit下使用PL/SQL Developer连接Oracle
    C#高级开发之 特性(Attribute)三
    K3单据表结构描述和相关关联SQL语句以及金蝶插件相关说明
    K3老单插件控制字段显示
    金蝶K3插件开发-控制单据焦点(BOS单据、工业单据)
    K3 单据,单据体自定义字段显示及时库存
    C#高级开发之反射(Reflection)二
    C#高级开发之泛型一
    python学习——协程
    python学习——线程
  • 原文地址:https://www.cnblogs.com/chry/p/10701827.html
Copyright © 2011-2022 走看看