接上篇, 本節內容主要講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'))
執行 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發送數據,是否也能安全透傳喃。咱們下回分解