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


    如果认真读过上文的朋友,应该已经明白了yield from实现的底层generator到caller的上传数据通道是什么了。本文重点讲yield from所实现的caller到coroutine的向下数据通道又是什么。注意我讲的是yield from做的”是什么“,而不是yield from"如何做到的"。这点区别非常大,大家一定要弄明白博主说的啥哈,不要弄混淆了。

    一. 系统模型。

    同样,仍然是上文的系统, 指把结束操作改为支持空行操作,它的业务需求是这样:

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

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

    3. 如果读入是空行,则打印"Error: Empty Line"

    二。 系统最初版本,这次我们把实现方式稍微变下,改成由一个writer和app实现,数据传输方向从上篇的reader到app, 变成app到writer。软件需求分别如下

    1. writer是一个协程coroutine。

      1)用来模拟文本接收,每次接收一行

           2)收到文本后,先打印">>" 然后打印收到的文本

           3)如果接收时产生EmptyException异常, 则打印"Error: Empty Line"

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

         1)while循环中,每次通过读取text列表中的一个item

         2)如果读到的不是空行,则直接将文本通过send发送给writer

         3) 如果读到的是空行,则触发writer产生EmptyException异常

    这个初版用python实现如下:

    # 定义一个EmptyException异常
    class EmptyException(Exception):
        pass
    
    # reader是一个协程, 它循环等待接收一行文本并打印输出
    def writer():
        while True:
            try:
                t = (yield)
                print ('>> %s' % t)
            except EmptyException:
                print('Error: Empty Line')
        
    # app是定义的一个简单应用,将reader读出的值打印出来
    def app(text):    
        w = writer()            
        w.send(None)            #激活writer
        for line in text:
            if line=='':
                w.throw(EmptyException)
            else:
                w.send(line)
                
    #启动app应用
    app(('a','b','','c')) 

    执行得到如下结果

    >> a
    >> b
    Error: Empty Line
    >> c

    三。新需求引入

    现在系统需求改变了,在文件开始输出之前,需要记录日志以满足运维需求,运维需求和原有业务无关。为了避免以后再次修改app应用,引入一个代理proxyWriter处理这些切面类需求。

    1)Writer只涉及底层文本打印输出,和之前一样。

    2)定义一个before方法。 运维需求省略具体实现,里面可以添加记录日志等运维相关的需求

    def before():   # 文件开始输出之前的额外处理,比如记录日志
        pass  

    3) app的改动:将要输出的文本发送给代理proxyWriter, 不再直接发送给writer,这样所有和app无关的运维相关需求都可以在代理中实现, 而且app和writer均无再做任何修改,所有运维需求带来的改动以后都被将封装在代理中。

    # app是定义的一个简单应用,将text读出, 并逐行发送给writer
    def app(text):    
        w = proxyWriter()            
        w.send(None)            #激活proxyWriter
        for line in text:
            if line=='':
                w.throw(EmptyException)
            else:
                w.send(line)

    4)代理proxyWriter的软件需求。

         4-1)不能对app传给writer的值做任何修改,原样下发给writer

         4-2)被app触发的异常EmptyException, 也需要下发给writer

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

    # proxyWriter是一个代理,它循环等待接收app发送的一行文本,并转发给writer, 自己不做任何文本处理
    def proxyWriter():
        before()                #处理运维相关需求
        w = writer()            
        w.send(None)            #激活writer
        while True:
            t = (yield)
            w.send ('>> %s' % t)

    5)完整代码如

    # 定义一个EmptyException异常
    class EmptyException(Exception):
        pass
    
    def before():
        pass
        
    # writer是一个协程, 它循环等待接收一行文本并打印输出
    def writer():
        while True:
            try:
                t = (yield)
                print ('>> %s' % t)
            except EmptyException:
                print('Error: Empty Line')
        
    # proxyWriter是一个代理,它循环等待接收app发送的一行文本,并转发给writer, 自己不做任何文本处理
    def proxyWriter():
        before()                #处理运维相关需求
        w = writer()            
        w.send(None)            #激活writer
        while True:
            t = (yield)
            w.send ('>> %s' % t)
    
    # app是定义的一个简单应用,将text读出, 并逐行发送给writer
    def app(text):    
        w = proxyWriter()            
        w.send(None)            #激活proxyWriter
        for line in text:
            if line=='':
                w.throw(EmptyException)
            else:
                w.send(line)
                
    #启动app应用
    app(('a','b','','c')) 
    View Code

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

    复制代码

    >> >> a
    >> >> b
    Traceback (most recent call last):
    File "writerproxy.py", line 37, in <module>
    app(('a','b','','c'))
    File "writerproxy.py", line 32, in app
    w.throw(EmptyException)
    File "writerproxy.py", line 23, in proxyWriter
    t = (yield)
    __main__.EmptyException

    复制代码

    四。 问题的引入

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

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

    2. EmptyException没有被正确处理

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

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

    第二个错误,是由于proxyWriter在向writer发消息时,没有处理EmptyException, 导致writer虽然有EmptyEception处理逻辑,但由于代理没把异常下传, 导致处理遗漏

    这两个错误的根本原因其实是一个为了将包含yield的业务代码从app中分离出去, proxyWriter采用的是用Send来转发app到writer的数据,由于这个转发操作必须由proxyWriter自己实现,所以它实际割裂了app和实际writer的数据下方通道。下面是基于send的修改版本,正确实现数据转换和传

    # proxyWriter是一个代理,它循环等待接收app发送的一行文本,并转发给writer, 自己不做任何文本处理
    def proxyWriter():
        before()                #处理运维相关需求
        w = writer()            
        w.send(None)            #激活writer
        while True:
            try:
                t = (yield)
                w.send(t)                         #接收app的数据后,需要转发到writer
            except EmptyException:               
                w.throw(EmptyException)           #处理app下发的EmptyException, 也需要下发到writer

    再次执行, 结果正确。

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

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

    def proxyWriter():
        before()                #处理运维相关需求
        yield from writer()

    这是yield from更加牛逼的地方,它使proxyWriterder无需在关心writerer与app之间数据通道,这个数据通道被yield from完全封装,对proxyWriter透明,而且proxyWriter完全无权干涉, 也不可能在有马大哈式的重复打印, app发送的是啥, yield from百分百保证了writer收到的就是啥,并且不管下发的是数据还是异常。除了代码简洁易懂,而且数据安全,牛逼不牛逼 !

    以上讲的是,yieldfrom如何搞定从上层的app到底层的协程(即writer)的数据通道,加上上一篇从底层到上层的传递, 所以说yield from实现了一个底层与顶层之间透明安全的数据双向传输通道。

    卧槽, 牛逼坏了,是不是 !

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

  • 相关阅读:
    PHP 通过Socket收发16进制数据,数据包格式
    Form 提交表 单页面刷新不跳转
    查看网段内正在使用的IP以及ip定位 ——CMD批处理循环
    深入浅出讲解:php的socket通信
    PHP读取XML值的代码 解析
    大端模式和小端模式 网络字节顺序与主机字节顺序
    寒假day07
    寒假day06
    寒假day05-spring框架
    寒假day04
  • 原文地址:https://www.cnblogs.com/chry/p/10703090.html
Copyright © 2020-2023  润新知