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

若是認真讀過上文的朋友,應該已經明白了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'))
View Code

執行 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之最詳最強解釋,一看就懂(三)

相關文章
相關標籤/搜索