句法上看, 協程與生成器類似, 都是定義體中包含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環境下使用,所以在此不再繼續擴展,之后再接著說這塊。