Python控制流程-協程(1)

句法上看, 協程與生成器類似, 都是定義體中包含yield關鍵字的函數。

具體的協程的使用:

1.在協程中yield通常出現在表達式的右邊,例如:datum = yield,可以產出值,也可以不產出--如果yield關鍵字后面沒有表達式,那么生成器產出None.

2.協程可能從調用方接受數據,調用方是通過send(datum)的方式把數據提供給協程使用,而不是next(...)函數,通常調用方會把值推送給協程。

3.協程可以把控制器讓給中心調度程序,從而激活其他的協程

協程的過程:

一個簡單的例子:

????????上述例子中,yield 的右邊沒有表達式,所以這里默認產出的值是None。因為一開始生成器還沒有啟動,不會停在yield處,所以需要先調用next()函數。在激活了協程之后,程序就會運行到x = yield。當我們調用send方法后yield會收到這個值并賦值給x,而當程序運行到協程定義體的末尾時和用生成器的時候一樣會拋出StopIteration異常。

? ??????協程在運行過程中有四個狀態:

1.GEN_CREATE:等待開始執行

2.GEN_RUNNING:解釋器正在執行,這個狀態一般看不到

3.GEN_SUSPENDED:在yield表達式處暫停

4.GEN_CLOSED:執行結束

? ??????因為send方法的參數會成為暫停的yield表達式的值, 所以, 僅當協程處于暫停狀態時才能調用send方法,例如sc.send('wolf')。不過,如果協程還沒激活(即'GEN_CREATED'),情況就不同了。因此,始終要調用next(sc)激活協程——也可以調用sc.send(None), 效果一樣。

? ??????最先調用next(sc)函數這一步通常稱為“預激”(prime)協程(讓協程向前執行到第一個yield表達式,準備好作為活躍的協程使用)。

? ? ? ? 通過以下的例子可以更全面了解協程在運行過程中的跳轉與輸出:

實例:使用協程計算平均值

? ? ? ? 上例是一個死循環,只要不斷向協程中傳值,就會一直計算平均值。

? ? ? ? 通過以上三個例子會發現,在使用協程的時候必須要通過next(...)方式激活協程,如果不預激,這個協程就無法使用。如果哪天在代碼中遺忘了那么就出問題了,所以有一種預激協程的裝飾器,可以幫助我們干這件事。

預激協程的裝飾器

? ? ? ? 如上所示,在每個使用協程的地方調用裝飾器,就能自動完成預激。很多框架都提供了處理協程的特殊裝飾器,也就不需要我們來手動編寫。比如我常用的Tornado框架中,就可以直接使用tornado.gen裝飾器。

? ??????使用yield from句法調用協程時,會自動預激,因此與上述樣例中@coroutine等裝飾器不兼容,之后會具體講述。

終止協程和異常處理

? ??????協程中未處理的異常會向上冒泡,傳給next函數或send方法的調用方(即觸發協程的對象)。比如我們在之前求平均值的例子中,不傳數字而傳字符串,就會有如下所示:

? ? ? ? 協程會如上所示拋出異常,并直接終止協程。如果嘗試繼續傳入正確的數值,會拋出協程已終止的異常:

? ? ? ? 上例中暗示了終止協程的一種方式:發送某個哨符值,讓協程退出。內置的None和Ellipsis等常量經常用作哨符值。Ellipsis的優點是,數據流中不太常有這個值。甚至可以把StopIteration類(類本身,而不是實例,也不拋出)作為哨符值;也就是說,是像這樣使用的:ag.send(StopIteration)。

????????從python2.5開始客戶端代碼在生成器對象上調用兩個方法,顯示的把異常發送給協程:

分別為:throw和close

generator.throw:

會讓生成器在暫停的yield表達式處拋出指定的異常,如果生成器處理了拋出的異常,代碼會向前執行到下一個yield表達式,而產出的值會成為調用generator.throw方法代碼的返回值。如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調用方的上下文中。

generator.close:

會讓生成器在暫停的yield表達式處拋出GeneratorExit異常。如果生成器沒有處理這個異常,或者拋出了StopIteration異常,調用方不會報錯,如果收到GeneratorExit異常,生成器一定不能產出值,否則解釋器會拋出RuntimeError異常。生成器拋出的異常會向上冒泡,傳給調用方。

具體使用樣例如下:

? ??????當傳入我們定義的異常時不會影響協程,協程不會停止,可以繼續send。但是如果是沒有處理的異常的時候,就會報錯,并且協程會被終止。

關于yield from

????????首先要知道,yield from是全新的語言結構。它的作用比yield多很多,因此人們認為繼續使用那個關鍵字多少會引起誤解。在其他語言中,類似的結構使用await關鍵字, 這個名稱好多了, 因為它傳達了至關重要的一點:在生成器gen中使用yield from subgen()時,subgen會獲得控制權,把產出的值傳給gen的調用方,即調用方可以直接控制subgen。與此同時,gen會阻塞,等待subgen終止。

? ??????yield from x表達式對x對象所做的第一件事是,調用iter(x),從中獲取迭代器,因此x可以是任何可迭代的對象。

? ??????下面是一個yield from可以簡化yield表達式的例子:

????????這兩種的方式的結果是一樣的,但是這樣看來yield from更加簡潔,但是yield from的作用可不僅僅是替代產出值的嵌套for循環。

????????yield from的主要功能是打開雙向通道,把最外層的調用方與最內層的子生成器連接起來,這樣二者可以直接發送和產出值,還可以直接傳入異常,而不用再像之前那樣在位于中間的協程中添加大量處理異常的代碼。

? ? ? ? 因為yield語法需要在python3環境下使用,所以在此不再繼續擴展,之后再接著說這塊。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 從語法上來看,協程和生成器類似,都是定義體中包含yield關鍵字的函數。yield在協程中的用法:在協程中yiel...
    JokerW閱讀 1,810評論 0 0
  • 在跨年會上第一次從羅胖哪里了解到了"人生算法"這個概念。挺抵觸的,感覺美好的人生不該用 算法 這樣 機械 刻...
    李建勇閱讀 587評論 3 2
  • 一年一度的返鄉潮又來了,這時間我并不擔心車票的事,而往往激動難眠。 或許在外漂迫了太久,越來越戀家,我知道那里才是...
    墨非魚閱讀 201評論 0 2
  • 前言 本章Linux會給大家帶來Linux遠程管理工具中的多個工具供大家參考學習。所有工具的共同點是:通過該應用程...
    YBshone閱讀 1,951評論 0 1
  • @所有人:需要參加信息技術提升線下培訓的教師,時間為明天一天,地點在師校(二職高)階梯教室。請在此報名,僅限五人。...
    啟強_3a4d閱讀 204評論 0 0