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

yield是python中一個很是重要的關鍵詞,全部迭代器都是yield實現的,學習python,若是不把這個yield的意思和用法完全搞清楚,學習python的生成器,協程和異步io的時候,就會完全懵逼。因此寫一篇總結講講yield的東西。html

分紅四塊來說, 這篇先說yield基本用法,後面會重點將yield from的牛逼之處python

一, 生成器中使用yield異步

語法形式:yield <表達式>函數

這種狀況,能夠簡單的把它理解爲 return <表達式>, 每次next調用,會觸發生成器從上一次中止的地方開始執行, 直到下一次遇到yield。 若是是第一次next,就是那個從函數開始知道第一個yield;之後每次next,則都是從上次yiled中止的地方開始, 繼續執行,直到遇到下一次yiled, 若是一直執行到函數結束,都沒有下個yield遇到, 則生成器運行終止,此時next會在生成器運行終止時,拋出StopIteration異常。 好比下面的代碼學習

 1 def generator():  2     print("gen-0: start")  3     for i in range(2):  4         print("gen-%s-a: i=%s" % (i,i))  5         yield i + 1                          # 可簡單理解爲 return i + 1, 並等待
 6         print("gen-%s-b: i=%s" % (i,i))  7     print("gen-c")  8     yield 3                                  # 可簡單理解爲 return 3, 並等待
 9     print("gen-d")                      # 此處繼續執行完成後,因爲後續在無其它yield語句,調用next的代碼會拋出StopIteration異常
10 
11 try: 12     g = generator()                          # 因爲generator是一個包含yield關鍵字的生成器,此處直接調用gengerator()只是返回該生成器實例,實際無任何輸出
13    print('main-0: start')  14     n = next(g)                              # next觸發生成器執行,因爲是第一次觸發,因此從generator循環外第2行和第4行的打印會輸出,到第5行遇到yield後返回i+1到主程序,第6行不會執行
15     print('main-1: n=%s' % n) 16     n = next(g)                              # next第二次觸發生成器執行,因爲是第二次觸發,上次執行到第5行, 此時從第6行開始執行, 將輸出第6行和第4行的打印, 並再次在第5行返回
17     print('main-2: n=%s' % n) 18     n = next(g)                              # next第三次觸發生成器執行,此次generator內部for循環已經完成,故執行完跳出循環執行到第8行後返回
19     print('main-3: n=%s' % n) 20     n = next(g)                              # next視圖第四次觸發生成器執行,從第9行開始執行,但此後generator已經所有執行完成,因此此時next會拋出StopIteration異常,賦值沒法完成
21     print('main-5: n=%s' % n)            # 異常產生, 此行將永遠不會被執行
22 except StopIteration: 23     print('StopIteration n=%s' % n)          # 第17行的next將觸發異常

上面這段代碼執行輸出以下, 各位看官自行對照代碼和詳細註釋很容易看懂spa

main-0: start
gen-0: start
gen-0-a: i=0
main-1: n=1
gen-0-b: i=0
gen-1-a: i=1
main-2: n=2
gen-1-b: i=1
gen-c
main-3: n=3
gen-d
StopIteration n=3 code

二, 能夠接收傳入參數值的yield協程

語法形式:x = yield <表達式>htm

next方式觸發生成器沒法傳入參數, 若是想觸發generator的同時, 傳入參數給生成器,是否能夠喃?答案是能夠的, 這時候須要作兩個改變,一是在生成器內增長變量接收該參數, 即本屆標題形式;二是外部調用時, 不使用next, 而採用用調用生成器的send方法便可blog

 1 def coroutine():                                    # 這個生成器,咱們其名coroutine, 意思是協程,協程和生成器都是使用yield來實現, 但本質上不一個概念
 2     print("coroutine: start")  3     for i in range(2):  4         print("coroutine-a: i=%s" % i)  5         x = yield i + 1                                # 由send傳入的參數值將被賦值給x, 注意i+1的值是被yield用來返回的,不會賦值給x !
 6         print("coroutine-b: i=%s, x=%s" % (i,x))  7  
 8 cr = coroutine()                                    # 建立一個生成器實例
 9 next(cr)                                            # 生成器的第一次觸發必須使用next,此時若是試圖用send發送參數,將致使異常敗,緣由是此時生成器還未啓動
10 try: 11     print("main-a:") 12     y = cr.send(0)                                # 調用生成器的send方法, 將10傳給生成器, 並觸發一次生成器的執行
13     print("main-b: y=%s" % y) 14     y = cr.send(1)                                # 調用生成器的send方法, 將1傳給生成器, 並觸發一次生成器的執行
15     print("main-c: y=%s" % y)               # 14行的消息發送將致使StopIteration異常產生, 此行將永遠不會被執行
16 except StopIteration: 17     print("StopIteration")

如下是執行後的輸出

coroutine: start coroutine-a:    i=0 main-a: coroutine-b:    i=0, x=0 coroutine-a:    i=1 main-b:         y=2 coroutine-b:    i=1, x=1 StopIteration

簡單再解釋一下,

  1. 第一次執行next時,生成器經過yield i + 1 返回1, 並停在第5行等待被喚醒, 此時賦值動做還沒有發生, x的值只是個None, 同時也爲執行後面的print。

  2. 生成器經過next進行過首次觸發後,能夠用send發送參數,好比第11行和12行,生成器內部變量先分別被賦值爲 0和1, 你們能夠對照輸出看

  3. 因爲生成器總共只有兩次yield的機會, next消耗一次,第一次send消耗一次,因此第二次send後,雖然不影響執行完參數傳遞給生成器的動做, 但因爲生成器自身找不下一次yield的機會,生成器執行終止。 致使send拋出StopIteration異常

三。生成器自己的返回值

不知道你們注意到沒有, 直接相似的cr = coroutine()   的調用,只是產生一個生成器實例,並沒執行。而經過yield的返回時, 生成器自己的邏輯實際上都是爲走完的, yield英文是退讓的意思,除了無限序列的生成器,生成器最終都會執行完成,那麼此時若是生成器經過return正常返回一個值,生成器的使用者能得到嗎 ?好比下面的代碼, 生成器正常結束後return的代碼, 使用方如何得到喃 ?好比下面代碼如何獲取第10行正常終止後的返回值10

1 def generator(): 2     yield 5                        
3     return 10                    # 這個返回值如何得到 ?
4 
5 cr = generator() 6 n = next(cr)                     # yield 5返回的值能夠被得到並賦值給n

答案是在StopIteration異常的處理邏輯中能夠得到,方法以下, 只須要在上面代碼後面繼續加上如下處理

try: next(cr) except StopIteration as e:        # 經過except ... as 將異常保存在變量e中
    rt = e.value                # 異常變量e的value值就是生成器經過return返回值

 嚴格意義上講, python將StopIteration定義爲一個異常多是不得已的事情, 由於這個異常的意思, 實際就是告訴使用者, 「我沒發生成下一個了, yield次數已經用完了, 個人使命結束了」, 從這個意義上講, StopIteration也能夠歸爲是正常邏輯, 因此強烈建議全部使用用生成器的地方,都應該要加上StopIteration異常處理。

四,不用next觸發生成器執行

生成器的首次觸發必定要使用next或者send(None)觸發,這個有時候比較麻煩, 有沒有不須要寫next的狀況喃。 答案是確定的。咱們能夠把generator放在循環裏面,好比把第一個例子的幾回next改爲一個for循環

def generator(): print("gen-0: start") for i in range(2): yield i + 1                          
    yield 3                                 

try: g = generator() for n in g:                            # for循環是經過next觸發生成器實現的, 內部會調用next(g)
        print(n) except StopIteration: print('StopIteration n=%s' % n)

將獲得下面的輸出, 由於在python裏面全部的迭代,包括循環語句,實際底層的實現都是經過生成器搞定的, 因此在for循環裏面, 其實是python內部實如今幫你調用next來觸發生成器.和咱們在第一例中的顯示依次調用next(g)是同樣的 能夠看到第一次打印了「start」輸出, 後面依次輸出1,2,3. 但沒有StopExeption拋出, 那是由於for的語意是循環次數和實際相同, 因此最後一次next被for內部消化了, 沒有暴露出來而已

gen-0: start 1
2
3

 

四。 再說 x = yield i, 生成器和協程

咱們在來看一下上面第二部分的例子, 這裏的x = yield i + 1語句, 實際上使函數自己同時具有了生產者和消費的功能, yield i + 1會讓每次執行產生一個值, 這是生成者,而 x = yiled又讓它能夠接收一個send所發送的值。這樣看起來同時具有了雙重功能, 但這倒是一種很差的用法, 應該儘可能避免。而是讓一個函數功能單一, 要嗎做爲生產器,要嗎做爲僅具有消費功能的協程。雖然協程和生成器都是用yield來實現的, 但不該該將兩者功能混淆, 這可能會致使一些難以理解的代碼,是不推薦這麼用的。 推薦的用發是分開,要嗎做爲生成器,要嗎做爲協程,不要讓一個函數同時兼備兩者的功能

第一節是做爲生成器的例子

做爲協程, 建議的方式是使用  x = (yield), 不要讓yiled生成任何值, 僅僅用於接收

def coroutine():                                    # 這個是協程,消費每次收到的值
     print("coroutine: start") While True: print("coroutine-a: i=%s" % i) x = (yield) print("coroutine-b: x=%s" % x)

 

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

相關文章
相關標籤/搜索