1. 程式人生 > >Python基礎學習之六yield

Python基礎學習之六yield

協程定義

協程的底層架構是在pep342 中定義,並在python2.5 實現的。

python2.5 中,yield關鍵字可以在表示式中使用,而且生成器API中增加了 .send(value)方法。生成器可以使用.send(...)方法傳送資料,傳送的資料會成為生成器函式中yield表示式的值。

協程是指一個過程,這個過程與呼叫方協作,產出有呼叫方提供的值。因此,生成器可以作為協程使用。

除了 .send(...)方法,pep342 和添加了 .throw(...)(讓呼叫方丟擲異常,在生成器中處理)和.close()(終止生成器)方法。

python3.3後,pep380對生成器函式做了兩處改動:

生成器可以返回一個值;以前,如果生成器中給return語句提供值,會丟擲SyntaxError異常。

引入yield from 語法,使用它可以把複雜的生成器重構成小型的巢狀生成器,省去之前把生成器的工作委託給子生成器所需的大量模板程式碼。

協程生成器的基本行為

首先說明一下,協程有四個狀態,可以使用inspect.getgeneratorstate(...)函式確定:

GEN_CREATED # 等待開始執行

GEN_RUNNING # 直譯器正在執行(只有在多執行緒應用中才能看到這個狀態)

GEN_SUSPENDED # 在yield表示式處暫停

GEN_CLOSED # 執行結束

#! -*- coding: utf-8 -*-import inspect# 協程使用生成器函式定義:定義體中有yield關鍵字。def simple_coroutine():

    print('-> coroutine started')    # yield 在表示式中使用;如果協程只需要從客戶那裡接收資料,yield關鍵字右邊不需要加表示式(yield預設返回None)

    x = yield

    print('-> coroutine received:', x)

my_coro = simple_coroutine()# 和建立生成器的方式一樣,呼叫函式得到生成器物件。

print(inspect.getgeneratorstate(my_coro))# 協程處於 GEN_CREATED (等待開始狀態)my_coro.send(None)# 首先要呼叫next()函式,因為生成器還沒有啟動,沒有在yield語句處暫停,所以開始無法傳送資料# 傳送 None 可以達到相同的效果 my_coro.send(None) next(my_coro)# 此時協程處於 GEN_SUSPENDED (在yield表示式處暫停)print(inspect.getgeneratorstate(my_coro))# 呼叫這個方法後,協程定義體中的yield表示式會計算出42;現在協程會恢復,一直執行到下一個yield表示式,或者終止。my_coro.send(42)

print(inspect.getgeneratorstate(my_coro))

執行上述程式碼,輸出結果如下

GEN_CREATED

-> coroutine started

GEN_SUSPENDED

-> coroutine received: 42# 這裡,控制權流動到協程定義體的尾部,導致生成器像往常一樣丟擲StopIteration異常Traceback (most recent call last):

  File "/Users/gs/coroutine.py", line 18, in <module>

    my_coro.send(42)

StopIteration

send方法的引數會成為暫停yield表示式的值,所以,僅當協程處於暫停狀態是才能呼叫send方法。如果協程還未啟用(GEN_CREATED 狀態)要呼叫next(my_coro) 啟用協程,也可以呼叫my_coro.send(None)

如果建立協程物件後立即把None之外的值發給它,會出現下述錯誤:

>>> my_coro = simple_coroutine()>>> my_coro.send(123)

Traceback (most recent call last):

  File "/Users/gs/coroutine.py", line 14, in <module>

    my_coro.send(123)

TypeError: can't send non-None value to a just-started generator

仔細看錯誤訊息

can't send non-None value to a just-started generator

最先呼叫next(my_coro) 這一步通常稱為”預激“(prime)協程---即,讓協程向前執行到第一個yield表示式,準備好作為活躍的協程使用。

再看一個兩個值得協程

def simple_coro2(a):

    print('-> coroutine started: a =', a)

    b = yield a

    print('-> Received: b =', b)

    c = yield a + b

    print('-> Received: c =', c)

my_coro2 = simple_coro2(14)

print(inspect.getgeneratorstate(my_coro2))# 這裡inspect.getgeneratorstate(my_coro2) 得到結果為 GEN_CREATED (協程未啟動)next(my_coro2)# 向前執行到第一個yield 處 列印 “-> coroutine started: a = 14”# 並且產生值 14 (yield a 執行 等待為b賦值)print(inspect.getgeneratorstate(my_coro2))# 這裡inspect.getgeneratorstate(my_coro2) 得到結果為 GEN_SUSPENDED (協程處於暫停狀態)my_coro2.send(28)# 向前執行到第二個yield 處 列印 “-> Received: b = 28”# 並且產生值 a + b = 42(yield a + b 執行 得到結果42 等待為c賦值)print(inspect.getgeneratorstate(my_coro2))# 這裡inspect.getgeneratorstate(my_coro2) 得到結果為 GEN_SUSPENDED (協程處於暫停狀態)my_coro2.send(99)# 把數字99傳送給暫停協程,計算yield 表示式,得到99,然後把那個數賦值給c 列印 “-> Received: c = 99”# 協程終止,丟擲StopIteration

執行上述程式碼,輸出結果如下

GEN_CREATED

-> coroutine started: a = 14GEN_SUSPENDED

-> Received: b = 28-> Received: c = 99Traceback (most recent call last):

  File "/Users/gs/coroutine.py", line 37, in <module>

    my_coro2.send(99)

StopIteration