python中和生成器協程相關的yield from之最詳最強解釋,一看就懂(三)

接上篇, 本節內容主要講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')) 
View Code

執行 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發送數據,是否也能安全透傳喃。咱們下回分解

 

下一篇 : python中和生成器協程相關的yield from之最詳最強解釋,一看就懂(四)

上一篇:  python中和生成器協程相關的yield from之最詳最強解釋,一看就懂(二)

相關文章
相關標籤/搜索