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

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

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

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

 

依然很差理解。我考虑了好几种方案,参阅了好多文章,都以为说不清楚,因此我这里经过一个很是简单模拟系统来一步步详解说明git

一. 系统模型。程序员

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

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

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

3. 若是文本所有成功读取,则最后打印“done”表示应用正常结束运维

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

1. reader是一个generator。spa

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

相关文章
相关标签/搜索