從生成器到協程 協程是指一個過程,這個過程與調用方協作,產出由調用方提供的值。生成器的調用方可以使用 .send(...)方法發送數據,發送的數據會成為yield表達式的值。因此,生成器可以作為協程使用。 從句法上看,生成器與協程都是包含yield關鍵字的函數。但是,在協程中,yield通常出現在表 ...
從生成器到協程
協程是指一個過程,這個過程與調用方協作,產出由調用方提供的值。生成器的調用方可以使用 .send(...)方法發送數據,發送的數據會成為yield表達式的值。因此,生成器可以作為協程使用。
從句法上看,生成器與協程都是包含yield關鍵字的函數。但是,在協程中,yield通常出現在表達式的右邊(* = yield *),可以產出值也可以不產出(yield關鍵字後邊沒有表達式,產出None)。
協程有四個狀態:
GEN_CREATED:等待開始執行
GEN_RUNNING:正在執行(只有在多線程應用或生成器對象自身調用getgeneratorstate函數可以看到此狀態)
GEN_SUSPENDED:在yield表達式處阻塞
GEN_CLOSED:執行結束
使用inspect.getgeneratorstate(...)函數可以查看當前協程的狀態。
使用協程的基本步驟為:
- 創建協程對象
- 調用next函數,激活協程
- 調用 .send(...)方法,推動協程執行並產出
一個累積求和的協程示例如下:
如上圖示例所示,協程中產出的值會返回給調用方,同時,通過yield將調用方傳入的參數賦值給yield表達式左邊的變數,並推動協程繼續執行。
終止協程和異常處理
因為協程使用生成器函數定義,因此遵循生成器的特性,當協程執行到定義體末尾時,會拋出StopIteration異常。如果協程在執行過程中發生了未處理的異常,協程會終止運行並將異常拋出,此時,試圖重新激活協程會拋出StopIteration異常。代碼示例:
示例代碼中,依然使用累積求和的協程,調用時因為傳入了字元串參數,導致協程因TpyeError異常而終止,再次試圖調用時,拋出了StopIteration異常。
調用方可以通過調用生成器對象 .throw(exc_type[, exc_value[, traceback]])方法,致使生成器在阻塞的yield表達式處拋出指定的異常。如果生成器處理了拋出的異常,代碼會向前執行到下一個yield表達式,產出的表達式會成為 .throw()方法的返回值;如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調用方的上下文中。代碼示例:
上圖示例代碼中,協程對TypeError進行了處理,所以當調用方將TpyeError異常發給協程時沒有終止;而當調用方將ValueError發給協程時,由於沒有處理,協程終止並將異常向上拋給調用方處理,調用方雖然捕獲了該異常,但試圖再次調用協程時,由於協程已終止,故拋出了StopIteration異常。
調用方可以通過生成器對象的 .close()方法,致使生成器在阻塞的yield表達式處拋出GeneratorExit異常。如果生成器沒有處理這個異常,或者拋出了StopIteration異常(通常指運行到程式結尾),調用方不會報錯。代碼示例:
上圖示例代碼中,調用 .close()方法後,調用方沒有報錯,協程終止且返回值為None,試圖再次激活協程對象時,會拋出StopIteration異常。
需要註意的是:如果在協程中捕獲了GeneratorExit異常,會導致RuntimeError;如果使用 .throw()方法直接將GeneratorExit異常發給協程,調用方會報錯並導致GeneratorExit異常。
讓協程返回值
有些協程不會產出值,而是在執行結束後返回一個值,而為了返回這個值,協程必須正常終止。代碼示例:
上圖示例中,協程不再產出值,通過send(None)結束協程,代碼執行到最後觸發StopIteration異常,而返回值作為StopIteration異常的一個屬性返回給調用方。
yield from
yield from是全新的語言結構,多用於嵌套生成器。其主要功能是開闢一個雙向通道,把最外層的調用方與最內層的子生成器連接起來,這樣二者可以發送/產出值,還可以直接傳入異常,而不用在位於中間層的協程中添加大量處理異常的代碼。簡言之即yield from可以方便的實現生成器嵌套調用並自動處理大部分異常。
理解yield from首先要理解三個概念:
- 調用方:指委派生成器的客戶端代碼
- 委派生成器:包含yield from <iterable>表達式的生成器函數
- 子生成器:從yield from表達式中<iterable>部分獲取的生成器
典型的調用邏輯為:客戶端代碼(調用方)調用委派生成器對象,委派生成器在yield from表達式處阻塞,此時調用方與子生成器之間的雙向通道打開,調用方可以直接把數據發給子生成器,子生成器把產出的值發給調用方。子生成器執行結束,解釋器拋出StopIteration異常,並把返回值附加到異常對象上,此時委派生成器恢復執行。委派生成器yield from語句自動處理子生成器拋出的StopIteration異常及附加在異常對象上的返回值。代碼示例如下:
註意:委派生成器執行結束時也會拋出StopIteration異常,這裡使用了永久迴圈+全局變數(不推薦)的方式避免委派生成器退出引發StopIteration異常且使客戶端能夠拿到子生成器返回的結果。實際應用中應視情況進行異常處理。子生成器StopIteration之外未處理的異常會向上冒泡傳給委派生成器處理,yield from表達式的值是子生成器終止時傳給StopIteration異常的第一個參數。python3.5以後引入了await關鍵字來替代yield from,使代碼更加簡潔清晰。
以上。