若是認真讀過上文的朋友,應該已經明白了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)用來模擬文本接收,每次接收一行代理
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實現了一個底層與頂層之間透明安全的數據雙向傳輸通道。
臥槽, 牛逼壞了,是否是 !