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:\github\python>

四。 问题的引入

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

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之最详最强解释,一看就懂(二)

posted on 2019-04-13 19:32  大象O无形  阅读(652)  评论(0编辑  收藏  举报

导航