Python協(xié)程原理全面分析
序章
yield item這行代碼會產(chǎn)出一個值,提供給next()的調(diào)用方;此外還會做出讓步,暫停執(zhí)行生成器,讓調(diào)用方繼續(xù)工作,知道需要使用另一個值再調(diào)用next()。調(diào)用方會從生成器中拉取值。
從語法上來看,協(xié)程與生成器類似,都是從定義體中包含yield關(guān)鍵字的函數(shù)??墒?,在協(xié)程中,yield通常出現(xiàn)在表達(dá)式的右邊(如data = yield),可以產(chǎn)出值,也可以不產(chǎn)出:如果yield關(guān)鍵字后面沒有表達(dá)式,那么生成器產(chǎn)出None。協(xié)程可能會從調(diào)用方接收數(shù)據(jù),不過調(diào)用方把提供數(shù)據(jù)給協(xié)程使用的方式是.send(data)方法。調(diào)用方會把值推送給協(xié)程。
yield關(guān)鍵字甚至可以不接受或傳出數(shù)據(jù)。不管數(shù)據(jù)如何流動,yield都是一種流程控制工具,使用它可以實現(xiàn)協(xié)作式多任務(wù);協(xié)程可以把控制器讓步給中心調(diào)度程序,從而激活其他的協(xié)程。
綜上,如果從根本上把yield視為控制流程的方式,這樣就好理解協(xié)程了
協(xié)程可以認(rèn)為是可以明確標(biāo)記有某種語法元素(yield from)的階段“暫停”函數(shù)
生成器如何進(jìn)化為協(xié)程
協(xié)程的框架是在Python2.5(2006年)實現(xiàn)的。自此之后,yield關(guān)鍵字可以在表達(dá)式中使用,并且生成器增加了.send(value)方法。生成器的調(diào)用方法可以使用.send()發(fā)送數(shù)據(jù),發(fā)送的數(shù)據(jù)會成為生成器中yield表達(dá)式的值。因此生成器可以作為協(xié)程使用。協(xié)程是指一個過程,這個過程與調(diào)用方協(xié)作,產(chǎn)出調(diào)用方法提供的值。
除了.send()方法,后續(xù)還增加了.throw()和.close()方法:前者作用是讓調(diào)用方拋出異常,在生成器中處理;后者作用是終止生成器。
用作協(xié)程的生成器的基本行為
示例,一個簡單的協(xié)程
def simple_coroutine(): print('-> coroutine started') x = yield print('-> coroutine received:', x) my_coro = simple_coroutine() print(my_coro) # 得到的是生成器對象 print(next(my_coro)) # None print(my_coro.send(42)) 打印 <generator object simple_coroutine at 0x00D957B0> -> coroutine started None -> coroutine received: 42 Traceback (most recent call last): File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 13, in <module> print(my_coro.send(42)) StopIteration
知識點:
- 定義體中x = yield 這種形式,如果協(xié)程只需要從客戶那接收數(shù)據(jù),那么產(chǎn)出值是None,所以next(my_coro)的值為None,這個值是隱式指定的,因為yield關(guān)鍵字右邊沒有表達(dá)式。
- 首先要調(diào)用next(my_coro),因為生成器還沒有啟動,沒在yield語句處暫停,所以一開始是無法發(fā)送數(shù)據(jù)的,首先要調(diào)用next(),到y(tǒng)ield關(guān)鍵字處暫停。
- 當(dāng)執(zhí)行到my_coro.send(42),協(xié)程定義體中的yield表達(dá)式會得到42,然后賦值給x;協(xié)程繼續(xù)運行到下一個yield表達(dá)式,或者終止。
- 最后,控制權(quán)流動到協(xié)程定義體的末尾,導(dǎo)致生成器像往常一樣拋出StopIteration異常
協(xié)程的四個狀態(tài)
協(xié)程可以身處四個狀態(tài)中的一個。獲取當(dāng)前狀態(tài)可以使用inspect.getgeneratorstate()函數(shù)確定,該函數(shù)會返回下面四種狀態(tài)的一個:
- 'GEN_CREATED' :等待開始執(zhí)行
- 'GEN_RUNNING' :解釋器正在執(zhí)行 (只有在多線程程序才能看到這個狀態(tài))
- 'GEN_SUSPENDED' :在yield表達(dá)式處暫停
- 'GEN_CLOSED' : 執(zhí)行結(jié)束
因為send方法的參數(shù)會成為暫停的yield表達(dá)式的值,所以僅當(dāng)協(xié)程處于暫停狀態(tài)時才能調(diào)用send方法,例如my_coro.send(42).。
不過,如果協(xié)程還沒激活(即狀態(tài)為GEN_CREATED),始終要調(diào)用next(my_coro)激活協(xié)程----也可以調(diào)用my_coro.send(None)激活。
ps:如果創(chuàng)建協(xié)程對象后立即把None之外的值發(fā)給它,會出現(xiàn)以下錯誤,清晰明了:TypeError: can't send non-None value to a just-started generator
最先調(diào)用next(my_coro)函數(shù)這一步通常稱為“預(yù)激”(prime)協(xié)程,即讓協(xié)程向前執(zhí)行到第一個yield表達(dá)式,準(zhǔn)備好作為活躍的協(xié)程使用。
from inspect import getgeneratorstate def simple_coro2(a): print('-> started: a=', a) b = yield a print('-> received: b=', b) c = yield a + b print('-> received: c=', c) my_coro2 = simple_coro2(14) print(getgeneratorstate(my_coro2)) print(next(my_coro2)) print(getgeneratorstate(my_coro2)) print(my_coro2.send(28)) print(my_coro2.send(99)) 打印 GEN_CREATED -> started: a= 14 14 GEN_SUSPENDED -> received: b= 28 42 -> received: c= 99 Traceback (most recent call last): File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 19, in <module> print(my_coro2.send(99)) StopIteration
知識點:
- 協(xié)程在yield關(guān)鍵字所在的位置暫停執(zhí)行。
- 在賦值語句中,=右邊的代碼在賦值前執(zhí)行。因此對于b = yield a這行代碼,等到客戶端代碼再激活協(xié)程時才會設(shè)定b的值。
示例-使用協(xié)程計算平均值
示例,一個累積計算平均值的函數(shù),使用協(xié)程實現(xiàn)
def averager(): total = 0.0 count = 0 average = 0 while True: num = yield average total += num count += 1 average = total / count aver = averager() print(aver.send(None)) # 預(yù)激協(xié)程 print(aver.send(2)) print(aver.send(4)) print(aver.send(6))
打印
0
2.0
3.0
4.0
知識點:
- 使用協(xié)程之前,需要預(yù)激協(xié)程,除了使用aver.send(None)還可以使用next(aver)
- 這個示例中,是個無限循環(huán)的,僅當(dāng)調(diào)用方在協(xié)程上調(diào)用.close()方法,或者沒有對協(xié)程的引用而被垃圾回收程序回收時,這個協(xié)程才會終止。
預(yù)激協(xié)程的裝飾器
如果不預(yù)激,那么協(xié)程沒什么用。
調(diào)用my_coro.send(x)之前,一定要先調(diào)用next(my_coro)。為了簡化協(xié)程的用法,有時候會使用一個預(yù)激裝飾器。
from inspect import getgeneratorstate from functools import wraps def coroutine(func): """預(yù)激協(xié)程的裝飾器,向前執(zhí)行到第一個yield表達(dá)式""" @wraps(func) def primer(*args, **kwargs): gen = func(*args, **kwargs) next(gen) return gen return primer @coroutine def averager(): total = 0.0 count = 0 average = 0 while True: num = yield average total += num count += 1 average = total / count aver = averager() print(getgeneratorstate(aver)) print(aver.send(2)) print(aver.send(4)) print(aver.send(6))
打印
GEN_SUSPENDED
2.0
3.0
4.0
可以看到,使用預(yù)激裝飾器裝飾了averager函數(shù)上之后,協(xié)助立即處于GEN_SUSPENED狀態(tài),因此這個協(xié)程已經(jīng)準(zhǔn)備好,可以接收值了。
很多框架都提供了處理協(xié)程的特殊裝飾器,不過不是所有裝飾器都用于預(yù)激協(xié)程,有些會提供其他服務(wù),例如勾入事件循環(huán)。
使用yeild from句法調(diào)用協(xié)程時,會自動預(yù)激,因此使用@coroutine等裝飾器不兼容。Python3.4標(biāo)準(zhǔn)庫中的asyncio.coroutine裝飾器不會預(yù)激協(xié)程,因此能兼容yeild from句法。
終止協(xié)程和異常處理
協(xié)程中未處理的異常會向上冒泡,傳給觸發(fā)協(xié)程的對象(next函數(shù)或send函數(shù)的調(diào)用方)。
未處理的異常會導(dǎo)致協(xié)程終止:
In [4]: aver.send(20)
Out[4]: 20.0
In [5]: aver.send(30)
Out[5]: 25.0
In [6]: aver.send('spam')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
TypeError: unsupported operand type(s) for +=: 'float' and 'str'
In [7]: aver.send(40)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
StopIteration:
以上可以聯(lián)想到,終止協(xié)程的一種方式:發(fā)送某個哨符值,讓協(xié)程退出。
比如內(nèi)置的None和Ellipsis(省略號...)常量經(jīng)常用作哨符值。Ellipsis的優(yōu)點是數(shù)據(jù)流中不太常有這個值。也有人把StopIteration作為哨符值,這樣:my_coro.send(StopIteration)
從Python2.5開始,可以在生成器對象調(diào)用兩個方法,顯式的把異常發(fā)給協(xié)程。
這兩個方法是throw拋和close:
generator.throw(exc_type[, exc_value[, traceback]])
使生成器在yeild表達(dá)式處拋出指定的異常。
如果生成器處理了拋出的異常,代碼會向前執(zhí)行到下一個yeild表達(dá)式處,而產(chǎn)生的值會成為調(diào)用generator.throw方法得到的返回值。
如果生成器沒有處理拋出的異常,異常會向上冒泡,傳給調(diào)用方的上下文中。
generator.close()
使生成器在暫停的yield表達(dá)式處拋出GeneratorExit異常。
如果生成器沒有處理這個異常,或者拋出了StopIteration異常(表示運行到結(jié)尾),調(diào)用方不會報錯。
如果收到GeneratorExit異常,生成器一定不能產(chǎn)出值,否則解釋器會拋出RuntimeError異常。
生成器拋出的其他異常會向上冒泡,傳給調(diào)用方。
示例,使用close和throw方法控制協(xié)程。
from inspect import getgeneratorstate class DemoException(Exception): pass def demo_exc_handling(): """異常處理demo""" print('-> coroutine started') while True: try: x = yield except DemoException: print('*** DemoException handled') # 處理異常 else: print('-> coroutine received: {!r}'.format(x)) # 如果沒有異常,顯示接收的值 exc_coro = demo_exc_handling() next(exc_coro) exc_coro.send(11) exc_coro.close() # 正常關(guān)閉協(xié)程 print(getgeneratorstate(exc_coro)) exc_coro2 = demo_exc_handling() next(exc_coro2) exc_coro2.throw(DemoException) # 把異常傳入?yún)f(xié)程后,如果有處理,不會終止協(xié)程 exc_coro2.send(22) print(getgeneratorstate(exc_coro2)) exc_coro2.throw(ZeroDivisionError) # 如果把無法處理的異常傳入,協(xié)程會終止 打印 -> coroutine started -> coroutine received: 11 GEN_CLOSED -> coroutine started *** DemoException handled -> coroutine received: 22 GEN_SUSPENDED Traceback (most recent call last): File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 33, in <module> exc_coro2.throw(ZeroDivisionError) File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 13, in demo_exc_handling x = yield ZeroDivisionError
知識點:
- 使用.close()關(guān)閉協(xié)程,沒有任何異常拋出
- 如果協(xié)程中有對應(yīng)的異常處理代碼,.throw()的異常不會終止協(xié)程。如果沒有異常處理,協(xié)程會終止
如果不管協(xié)程如何結(jié)束都想要做些清理工作的話,要把協(xié)程定義體匯總相關(guān)的代碼放入try/finally塊中
def demo_exc_handling(): """異常處理demo""" print('-> coroutine started') try: while True: try: x = yield except DemoException: print('*** DemoException handled') # 處理異常 else: print('-> coroutine received: {!r}'.format(x)) # 如果沒有異常,顯示接收的值 finally: print('-> coroutine ending.') print('do something.')
獲取協(xié)程返回值
下面是averager累積求平均數(shù)的另一個版本,這個版本不會隨著增加元素返回平均值,而是最后返回一個值。
from collections import namedtuple from functools import wraps def coroutine(func): """預(yù)激協(xié)程裝飾器""" @wraps(func) def prime(*args, **kwargs): gen = func(*args, **kwargs) next(gen) return gen return prime Result = namedtuple('Result', 'count average') @coroutine def averager(): count = 0 average = 0.0 total = 0 while True: item = yield if item is None: break # 為了獲取返回值,協(xié)程必須正常終止,因此要有個判斷,以便退出累計循環(huán) count += 1 total += item average = total / count return Result(count, average) # 最終返回一個namedtuple,包含信息 ave = averager() ave.send(2) ave.send(4) print(ave.send(None)) 打印 Traceback (most recent call last): File "C:/Users/lijiachang/PycharmProjects/collect_demo/test2.py", line 39, in <module> print(ave.send(None)) StopIteration: Result(count=2, average=3.0)
知識點:
- 使用ave.send(None)發(fā)送None終止循環(huán),或者使用next(ave)也可以。結(jié)果就是導(dǎo)致協(xié)程結(jié)束,返回結(jié)果
- 代碼中break跳出了while循環(huán),導(dǎo)致運行到定義體結(jié)束,也就拋出StopIteration很正常。
- 異常對象的value保存著return返回的值。return表達(dá)式的值會偷偷傳給調(diào)用方,賦值給StopIteration異常的一個屬性。這樣做有點不合常理,但是能保留生成器對象的常規(guī)行為:耗盡時拋出StopIteration異常。
既然協(xié)程定義體中return返回值,是寄托到了異常的value值中,那么就捕獲異常:
try: print(ave.send(None)) except StopIteration as e: result = e.value print(result) 打印 Result(count=2, average=3.0)
獲取協(xié)程的返回值雖然要繞個圈子,但是這是PEP 380定義的方式。
但是我們平時不需要這樣做,因為yield from結(jié)構(gòu)會在內(nèi)部自動捕獲StopIteration異常。這種處理方式和for循環(huán)處理StopIteration異常的方式一樣:循環(huán)機(jī)制會讓給用戶易于理解的方式處理異常。
而且,對yeild from結(jié)構(gòu)來說,不僅可以捕獲StopIteration異常,還會把異常的value屬性值變?yōu)閥eild from的值。
所以接下來要介紹yield from結(jié)構(gòu)
使用yield from
要知道yield from是全新的語言結(jié)構(gòu)。它的作用比yield多得多,因此人們認(rèn)為繼續(xù)使用yield 關(guān)鍵字多少會引起誤解。
在Python3.4之后,被await關(guān)鍵字代替。
在生成器gen中使用yeild from subgen()時,subgen會獲得控制權(quán),把產(chǎn)出的值傳給gen的調(diào)用方,即調(diào)用方直接可以控制subgen來得到產(chǎn)出值。以此同時,gen會阻塞,帶燈subgen終止。
yield from x表達(dá)式對x對象所做的第一件事是,調(diào)用iter(x),從中獲取迭代器。因此x是任何可迭代對象。
yield from 結(jié)構(gòu)不是簡單的替代產(chǎn)出值的嵌套for循環(huán),而是把職責(zé)委托給子生成器的句法。
yield from 的主要功能是打開雙向通道,把最外層的調(diào)用方和最內(nèi)層的子生成器連接起來,
這樣二者可以直接發(fā)送和產(chǎn)出值,還可以直接傳入異常,而不用在位于中間的協(xié)程中添加大量打異常處理。有了這個結(jié)構(gòu),協(xié)程可以通過以前不可能的方式委托職責(zé)。
相關(guān)的專業(yè)術(shù)語:
委派生成器
包含yield from <iterable> 表達(dá)式的生成器函數(shù)。
子生成器
從yield from表達(dá)式中<iterable>部分獲取的生成器。
調(diào)用方
指代調(diào)用委派生成器的客戶端代碼。
委派生成器在yield from表達(dá)式處暫停時,調(diào)用方可以直接把數(shù)據(jù)發(fā)給子生成器,子生成器再把產(chǎn)生的值發(fā)給調(diào)用方。子生成器返回之后,解釋器會拋出StopIteration異常,并把返回值附加到異常對象上(異常的value屬性),此時委派生成器會恢復(fù)。
下面的示例,用于說明yield from結(jié)構(gòu)的用法
from collections import namedtuple from functools import wraps Result = namedtuple('Result', 'count average') # 作為子生成器使用 def averager(): count = 0 average = 0.0 total = 0 while True: item = yield if item is None: break count += 1 total += item average = total / count return Result(count, average) # 返回的結(jié)果最后會成為grouper函數(shù)中的yield from中的值 # 委派生成器 def grouper(results, key): while True: # 每次循環(huán)都會產(chǎn)生一個新的averager實例,每個實例都作為協(xié)程使用的生成器對象 results[key] = yield from averager() # grouper每次接受的到值都通過yield from處理,通過管道傳給averager實例 # 客戶端代碼,即調(diào)用方 def main(data): results = {} for key, values in data.items(): group = grouper(results, key) # group作為協(xié)程使用 next(group) # 預(yù)激協(xié)程 for value in values: group.send(value) # 把value傳給grouper,最終到達(dá)的是averager函數(shù)的item = yield那行。 group.send(None) # 把None傳給grouper,讓當(dāng)前averager實例終止,讓grouper繼續(xù)運行。 print(results) data = { 'girls;kg': [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5], 'girls;m': [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43], 'boys;kg': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3], 'boys;m': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46], } if __name__ == "__main__": main(data)
知識點:
- grouper會在yield from表達(dá)式處暫停,等待averager實例處理客戶端發(fā)來的值。averager實例運行完畢后,返回的值綁定到了results[key]上,while循環(huán)會不斷創(chuàng)建averager實例,處理更多的值
- 對于委派生成器grouper來說,永遠(yuǎn)不知道傳入的值是什么。因為當(dāng)value值傳給grouper,最終到達(dá)的是averager函數(shù)的item = yield那行。
group.send(None) 的作用非常重要,它讓當(dāng)前的averager實例終止,然后在創(chuàng)建一個新的averager實例。如果沒有它,results結(jié)果中不會有任何內(nèi)容。
下面說明沒有g(shù)roup.send(None) 時,最終的運作方式:
- 外層for循環(huán)每次迭代會新建一個grouper實例,賦值給group變量;group是委派生成器
- 調(diào)用next(group),是預(yù)激委派生成器grouper,此時進(jìn)入while Ture循環(huán),調(diào)用子生成器averager后,在yield from表達(dá)式處暫停
- 內(nèi)層for循環(huán)調(diào)用group.send(value),直接把值傳給子生成器averager。同時,當(dāng)前的grouper實例(group)在yield from表達(dá)式處暫停。
- 內(nèi)層for循環(huán)結(jié)束后,group實例依舊在yield from表達(dá)式處暫停,因此,grouper函數(shù)定義體中為results[key]賦值的語句還沒有執(zhí)行。
- 如果外層for循環(huán)的末尾沒有g(shù)roup.send(None),那么averager子生成器永遠(yuǎn)不會終止,委派生成器group永遠(yuǎn)不會再次激活,因此永遠(yuǎn)不會為results[key]賦值
- 外層for循環(huán)重新迭代時會新建一個grouper實例,然后綁定到group變量上。前一個grouper實例以及它創(chuàng)建的未終止的averager子生成器實例,會被垃圾回收程序回收。
上面想說明的關(guān)鍵一點是,如果子生成器不終止,委派生成器會在yield from表達(dá)式處永遠(yuǎn)暫停。
如果這樣,程序不會向前進(jìn)行,因為yield from把控制權(quán)交給了客戶端代碼(即委派生成器的調(diào)用方)。
以上的示例和說明,展示了yield from結(jié)構(gòu)最簡單的用法,只有委派生成器和一個子生成器。因為委派生成器相當(dāng)于管道,所以可以把任意數(shù)量個委派生成器連接在一起:一個委派生成器使用yield from調(diào)用一個子生成器,而那個子生成器本身也是委派生成器,使用yield from調(diào)用另一個生成器,以此類推。最終,這個鏈條要以一個只使用yield 表達(dá)式的簡單生成器結(jié)束,或者以任何可迭代對象結(jié)束。
任何yield from鏈條都必須由客戶驅(qū)動,在最外層委派生成器上調(diào)用next()函數(shù)或.send()方法??梢杂胒or循環(huán)等隱式調(diào)用。
yield from的意義
在PEP 380中有這么一段話,yield from的作者Greg Ewing的草稿,可以粗略的解釋:
把迭代器當(dāng)作生成器使用,相當(dāng)于 把子生成器的定義體內(nèi)聯(lián)在yield from表達(dá)式中。此外,子生成器可以執(zhí)行return語句,返回一個值,而返回的值會成為yield from表達(dá)式的值。
批準(zhǔn)后的PEP 380,分六點說明了yield from的行為:
- 子生成器產(chǎn)出的值都直接傳給委派生成器的調(diào)用方(即客戶端代碼)
- 使用send()方法發(fā)送給委派生成器的值都直接傳給子生成器。如果發(fā)送的值是None,那么會調(diào)用子生成器的__next__()方法。如果發(fā)送的值不是None,那么會調(diào)用子生成器的send()方法。如果調(diào)用的方法拋出StopIteration異常,那么委派生成器會恢復(fù)運行。任何的其他異常會向上冒泡,傳給委派生成器。
- 生成器退出時,生成器或者子生成器中的return expr表達(dá)式會觸發(fā)StopIteration(expr)異常拋出。
- yield from表達(dá)式的值,是子生成器終止時傳給StopIteration異常的第一個參數(shù)。
- 傳入委派生成器的異常,除了GeneratorExit之外都傳給子生成器的throw()方法。如果調(diào)用throw()方法時拋出StopIteration異常,委派生成器恢復(fù)運行。StopIteration之外的異常會向上冒泡,傳給委派生成器。
- 如果把GeneratorExit異常傳給委派生成器,或者在委派生成器上調(diào)用close()方法,那么會在子生成器上調(diào)用close()方法(前提是有此方法)。如果調(diào)用close()方法導(dǎo)致異常拋出,那么異常會向上冒泡,傳給委派生成器;否則,委派生成器拋出GeneratorExit異常。
yield from的具體語義難以理解,尤其是最后兩點。
Greg Ewing還使用了偽代碼,演示了yield from的行為。下面是把原40行的偽代碼簡化了的邏輯,因為原40行代碼難以理解。假設(shè)了客戶端代碼有在委派生成器上調(diào)用.throw()和.close()方法,假設(shè)子生成器不會拋出異常,而是一直運行到終止,讓解釋器拋出StopIteration異常。
下面列出的偽代碼,是對這行代碼的擴(kuò)充:RESULT = yield from EXPR
"""RESULT = yield from EXPR 的偽代碼""" _i = iter(EXPR) # EXPR 為任何可迭代對象,獲取迭代器_i使用iter()函數(shù),這里是獲取子生成器 try: _y = next(_i) # 預(yù)激子生成器,把結(jié)果保存在_y中,作為產(chǎn)出的第一個值 except StopIteration as _e: _r = _e.value # 如果拋出StopIteration異常,獲取異常對象中的value屬性,賦值給_r 這是最簡單情況下的返回值 else: while 1: # 運行這個循環(huán)時,委派生成器會阻塞,值作為調(diào)用方和子生成器之間的通道 _s = yield _y # 產(chǎn)出子生成器當(dāng)前產(chǎn)出的元素;等待調(diào)用方發(fā)送_s保存的值。 try: _y = _i.send(_s) # 嘗試讓子生成器向前執(zhí)行,轉(zhuǎn)發(fā)調(diào)用方發(fā)送的_s except StopIteration as _e: # 如果子生成器拋出StopIteration異常,獲取異常對象中的value屬性,賦值給_r;然后退出循環(huán),委派生成器恢復(fù)運行 _r = _e.value break RESULT = _r # 返回的結(jié)果是_r,即是整個yield from表達(dá)式的值
RESULT = _r # 返回的結(jié)果是_r,即是整個yield from表達(dá)式的值
- _i : 迭代器,子生成器
- _y : 產(chǎn)出的值,子生成器產(chǎn)出的值
- _r : 結(jié)果,最終的結(jié)果,即子生成器運行結(jié)束后yield from表達(dá)式的值
- _s : 發(fā)送的值,調(diào)用方發(fā)給委派生成器的值,這個值會轉(zhuǎn)發(fā)給子生成器
- _e : 異常對象
但是,現(xiàn)實情況會復(fù)雜一些,因為客戶要對.throw()和.close()方法調(diào)用,二者兩個方法的執(zhí)行操作,必須傳入子生成器。
子生成器可能只是純粹的迭代器,不支持.throw和.close()方法,因此yield from結(jié)構(gòu)邏輯必須處理這種情況。
如果子生成器實現(xiàn)了這兩個方法,而在子生成器內(nèi)部,這兩個方法都會觸發(fā)異常拋出,這種情況也必須由yield from機(jī)制處理。
調(diào)用方可能會無緣無故的讓子生成器自己拋出異常,實現(xiàn)yield from結(jié)構(gòu)時要處理這種情況。
最后,為了優(yōu)化,如果調(diào)用方調(diào)用next函數(shù)或者send方法,都要轉(zhuǎn)交職責(zé),在子生成器上調(diào)用next函數(shù),僅當(dāng)調(diào)用方發(fā)送的值不是None時,才使用子生成器的send方法。
完整的偽代碼,參見《流暢的Python》第401頁
使用協(xié)程做離散事件仿真
協(xié)程能自然地表述很多算法,例如仿真、游戲、異步IO,以及其他事件驅(qū)動型變成形式或協(xié)作式多任務(wù)。--Guido
協(xié)程是asyncio包的基礎(chǔ)構(gòu)。通過仿真系統(tǒng)能夠說明如何使用協(xié)程代替線程實現(xiàn)并發(fā)活動。
離散事件仿真(DES:Discrete Event Simulation)是一種把系統(tǒng)建模成一系列事件的仿真類型。
在離散事件仿真中,仿真“鐘”向前推進(jìn)的量不是固定的,而是直到推進(jìn)到下一個事件模型的模擬時間。假如我們抽象模擬出租車的運營過程,其中一個事件是乘客上車,下一個事件則是乘客下車。使用離散事件仿真可以在不到一秒鐘的時間內(nèi)模擬一年的出租車運營過程。這與連續(xù)仿真不同,連續(xù)仿真的仿真鐘以固定的量不斷向前前進(jìn)。
顯然,回合制游戲就是離散事件仿真的例子:游戲的狀態(tài)只在玩家操作時變化,而且一旦玩家決定下一步怎么走了,仿真鐘就會凍結(jié)。而實時游戲則是連續(xù)仿真,仿真鐘一直在運行,游戲的狀態(tài)在一秒鐘內(nèi)更新很多次。
這兩種仿真類型都能使用多線程或在單線程中使用面向事件的編程技術(shù)(例如事件循環(huán)驅(qū)動的回調(diào)或協(xié)程)實現(xiàn)??梢哉f,為了實現(xiàn)連續(xù)仿真,在多個線程中處理實時并行的操作更自然。而協(xié)程恰好為實現(xiàn)離散事件仿真提供了合理的抽象。SimPy是一個實現(xiàn)離散事件仿真的Python包,通過一個協(xié)程表示離散事件仿真系統(tǒng)的各個進(jìn)程。
到此這篇關(guān)于Python協(xié)程原理全面分析的文章就介紹到這了,更多相關(guān)Python協(xié)程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Django filter動態(tài)過濾與排序?qū)崿F(xiàn)過程解析
這篇文章主要介紹了Django filter動態(tài)過濾與排序?qū)崿F(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11keras.layers.Layer中無法定義name的問題及解決
這篇文章主要介紹了keras.layers.Layer中無法定義name的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02Python django搭建layui提交表單,表格,圖標(biāo)的實例
今天小編就為大家分享一篇Python django搭建layui提交表單,表格,圖標(biāo)的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11python中復(fù)數(shù)的共軛復(fù)數(shù)知識點總結(jié)
在本篇內(nèi)容里小編給大家整理的是關(guān)于python中復(fù)數(shù)的共軛復(fù)數(shù)知識點總結(jié),有需要的朋友們可以學(xué)習(xí)下。2020-12-12python數(shù)據(jù)操作之lambda表達(dá)式詳情
這篇文章主要介紹了python數(shù)據(jù)操作之lambda表達(dá)式詳情,文章基于python的相關(guān)資料展開lambda表達(dá)式具體的內(nèi)容,感興趣的小伙伴可以參考一下2022-05-05Django中更新多個對象數(shù)據(jù)與刪除對象的方法
這篇文章主要介紹了Django中更新多個對象數(shù)據(jù)與刪除對象的方法,Django是Python重多各色框架中人氣最高的一個,需要的朋友可以參考下2015-07-07