若是认真读过上文的朋友,应该已经明白了yield from实现的底层generator到caller的上传数据通道是什么了。本文重点讲yield from所实现的caller到coroutine的向下数据通道又是什么。注意我讲的是yield from作的”是什么“,而不是yield from"如何作到的"。这点区别很是大,你们必定要弄明白博主说的啥哈,不要弄混淆了。html
一. 系统模型。python
一样,仍然是上文的系统, 指把结束操做改成支持空行操做,它的业务需求是这样:程序员
1. 须要读取一段放在一个常量列表中的文本, 每一个item表示一行文本, 空行用空字符串''表示。安全
2. 每读入一行,若是不是空行,则先打印双小于号 "<<",而后打印读入的文本行app
3. 若是读入是空行,则打印"Error: Empty Line"运维
二。 系统最第一版本,此次咱们把实现方式稍微变下,改为由一个writer和app实现,数据传输方向从上篇的reader到app, 变成app到writer。软件需求分别以下ide
1. writer是一个协程coroutine。spa
1)用来模拟文本接收,每次接收一行3d
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实现了一个底层与顶层之间透明安全的数据双向传输通道。
卧槽, 牛逼坏了,是否是 !