如果认真读过上文的朋友,应该已经明白了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'))

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

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

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

    yield是python中一个非常重要的关键词,所有迭代器都是yield实现的,学习python,如果不把这个yield的意思和用法彻底搞清楚,学习python的生成器,协程和异步io的时候,就会彻底 ...

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

    一. 从列表中yield  语法形式:yield from <可迭代的对象实例> python中的列表是可迭代的, 如果想构造一个生成器逐一产生list中元素,按之前的yield语法,是在 ...

  3. Python | 详解Python中的协程,为什么说它的底层是生成器?

    今天是Python专题的第26篇文章,我们来聊聊Python当中的协程. 我们曾经在golang关于goroutine的文章当中简单介绍过协程的概念,我们再来简单review一下.协程又称为是微线程, ...

  4. Python中的协程,为什么说它的底层是生成器?

    我们曾经在golang关于goroutine的文章当中简单介绍过 协程 的概念,我们再来简单review一下.协程又称为是微线程,英文名是Coroutine.它和线程一样可以调度,但是不同的是线程的启 ...

  5. Python协程:从yield/send到async/await

    这个文章理好了脉落. http://python.jobbole.com/86069/ 我练 习了一番,感受好了很多... Python由于众所周知的GIL的原因,导致其线程无法发挥多核的并行计算能力 ...

  6. (转)python协程2:yield from 从入门到精通

    原文:http://blog.gusibi.com/post/python-coroutine-yield-from/ https://mp.weixin.qq.com/s?__biz=MzAwNjI ...

  7. python装饰器,迭代器,生成器,协程

    python装饰器[1] 首先先明白以下两点 #嵌套函数 def out1(): def inner1(): print(1234) inner1()#当没有加入inner时out()不会打印输出12 ...

  8. Python异步IO之协程(一):从yield from到async的使用

    引言:协程(coroutine)是Python中一直较为难理解的知识,但其在多任务协作中体现的效率又极为的突出.众所周知,Python中执行多任务还可以通过多进程或一个进程中的多线程来执行,但两者之中 ...

  9. [转载]Python 3.5 协程究竟是个啥

    http://blog.rainy.im/2016/03/10/how-the-heck-does-async-await-work-in-python-3-5/ [译] Python 3.5 协程究 ...

随机推荐

  1. 使用windows命令和iconv.exe批量转换文件编码

    iconv是知名的开源跨平台编码转换库,iconv.exe是iconv库在windows下的命令行工具,iconv.exe的一般用法:iconv.exe -f gbk -t utf-8 gbk.txt ...

  2. XP环境下C# 调用Pocess.start()时提示文件找不到的错误解决办法

    错误提示如下: System.ComponentModel.Win32Exception (0x80004005): 系统找不到指定的文件. 在 System.Diagnostics.Process. ...

  3. 【HANA系列】SAP HANA XS使用JavaScript数据交互详解

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA XS使用Jav ...

  4. Win7系统system进程句柄数一直增加解决方案

    公司内部最近有个服务端的同事电脑句柄数一开机就一直增加 一台Windows7x64系统16G 其实物理内存使用情况在开机后并没有太大的变化,但虚拟内存占用明显在不停的增加. 我通过“任务管理器”一直也 ...

  5. Linux运维之如何查看目录被哪些进程所占用,lsof命令、fuser命令

    之前将一块硬盘挂载到某个目录下,但是现在我想卸载掉这块硬盘,无论如何都umount不了,有些同学可能说需要加上 -f 参数强制卸载,理论上是可以的,但是在我这里依然不起作用,比如: [root@:vg ...

  6. PLS-00306: 调用 'SYNCRN' 时参数个数或类型错误

    System.Data.OracleClient.OracleException (0x80131938): ORA-00604: 递归 SQL 级别 1 出现错误 ORA-06550: 第 1 行, ...

  7. tkinter学习系列之(五)Checkbutton控件

    目录 目录 前言 (一)基本属性 (二)案例 1.简单的复选框 2.组合复选框 目录 前言 复选框:可以同时多选的一组框,其只有两种状态,选中与未选中. (一)基本属性 (1)说明: tkinter里 ...

  8. JAVA中实现单例(Singleton)模式的八种方式

    单例模式 单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例.即一个类只有一个对象实例. 基本的实现思路 单 ...

  9. Oracle11g链接提示未“在本地计算机注册“OraOLEDB.Oracle”解决方法

    当 用,Provider=OraOLEDB.Oracle方式访问ORACLE11g数据库.出现 未在本地计算机注册“OraOLEDB.Oracle”提供程序提示.解决方案如下: 客户端环境:Win7 ...

  10. Django之ORM查询进阶

    基于双下划线的双表查询 分组与聚合函数 基于双下划线的双表查询 Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系.要做跨关系查询, ...