Python實現(xiàn)協(xié)程(三)

一. 讓協(xié)程返回值

下面的例子,我們再次改版之前計算平均值的協(xié)程函數(shù)押赊,這一版本的協(xié)程函數(shù)每次被激活時饺藤,不會自動產(chǎn)出平均值包斑,而是在最后返回一個值。

from collections import namedtuple
 
Result = namedtuple('Result', 'count average')
 
 
def average():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)

為了返回值涕俗,協(xié)程必須正常終止罗丰,因此 average 中有個判斷條件,以便退出累計循環(huán)再姑。返回值為 namedtuple萌抵,包含 countaverage 兩個字段。

注:Python3.3 之前如果生成器返回值元镀,解釋器會報句法錯誤绍填。

演示 1 發(fā)送 None 會終止循環(huán),導(dǎo)致協(xié)程結(jié)束

注意:協(xié)程結(jié)束栖疑,協(xié)程對象會拋出 StopIteration 異常讨永。return 表達(dá)式的值通過異常對象 StopIteration 傳遞給調(diào)用方。這樣做有點不合常理遇革,但是能保留生成器對象的常規(guī)行為卿闹,即在耗盡時拋出 StopIteration 異常。

演示 2 獲取協(xié)程 return 的值

捕獲 StopIteration 異常萝快,并通過異常對象的 value 屬性獲取 average 返回的值锻霎。

下面我們介紹的 yield from 結(jié)構(gòu)會在內(nèi)部自動捕獲 StopIteration 異常,這種處理方式與 for 循環(huán)處理 StopIteration 異常的方式一樣:循環(huán)機制使用用戶易于理解的方式處理異常杠巡。

yield from 結(jié)構(gòu)來說量窘,解釋器不僅會捕獲 StopIteration 異常,還會把 value 屬性的值變?yōu)?yield from 表達(dá)式的值氢拥。

二. 使用 yield from

首先要明確蚌铜,yield from 是全新的語法結(jié)構(gòu),其功能要比 yield 豐富嫩海。

:在 Python 3.5 以后的版本中使用 async冬殃、await 關(guān)鍵字取代 yield from,雖然語法上略有差異叁怪,但是原理是一樣审葬。理解了 yield from ,那么 asyncawait 的使用自然水到渠成奕谭。

演示 1 yield from 可用于簡化 for 循環(huán)中的 yield 表達(dá)式

演示 2 使用 yield from 鏈接可迭代對象

yield from x 表達(dá)式對 x 對象所做的第一件事就是調(diào)用 iter(x) 涣觉,從中獲取迭代器。因此 x 可以是任何可迭代的對象血柳。

如果 yield from 結(jié)果唯一的作用是替代產(chǎn)出值的 for 循環(huán)官册,這個結(jié)果可能永遠(yuǎn)不會被添加到 Python 語言中。

yield from 的主要功能是打開雙向通道难捌,把最外層的調(diào)用方與最內(nèi)層的子生成器連接起來膝宁,這樣二者可以直接發(fā)送和產(chǎn)出值鸦难,還可以直接傳入異常,而不用在位于中間的協(xié)程中添加大量處理異常的樣板代碼员淫。

在舉例前合蔽,我們先來了解幾個專門的術(shù)語:

  • 委派生成器:包含 yield from <iterable> 表達(dá)式的生成器函數(shù)。
  • 子生成器:從 yield from 表達(dá)式中 <iterable> 部分獲取的生成器介返。
  • 調(diào)用方:調(diào)用委派生成器的客戶端代碼拴事。在適當(dāng)?shù)恼Z境中,將使用“客戶端”代指“調(diào)用方”映皆,以便于委派生成器(也是調(diào)用方挤聘,因為它調(diào)用了子生成器)區(qū)分開。

委派生成器在 yield from 表達(dá)式處暫停時捅彻,調(diào)用方可以直接把數(shù)據(jù)發(fā)給子生成器,子生成器再把產(chǎn)出的值發(fā)給調(diào)用方鞍陨。子生成器返回之后步淹,解釋器會拋出 StopIteration 異常,并把返回的值附加到異常對象上诚撵,此時委派生成器會恢復(fù)缭裆。

示例 使用 yield from 計算平均值,并輸出統(tǒng)計報告

from collections import namedtuple
 
Result = namedtuple('Result', 'count, average')
 
 
# 子生成器
def average():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)
 
 
# 委派生成器
def grouper(results, key):
    while True:
        results[key] = yield from average()
        print(results[key])
 
 
# 客戶端代碼寿烟,即調(diào)用方
def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        next(group)
        for value in values:
            group.send(value)
        group.send(None)
    report(results)
 
 
# 輸出報告
def report(results):
    print('-'*20, 'report', '-'*20)
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print(f'{result.count:2} {group:5} averaging {result.average:.2f}{unit}')
 
 
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)

運行結(jié)果:外層 for 循環(huán)每次迭代會產(chǎn)生一個 grouper 實例澈驼,賦值給 group 變量,group 是委派生成器筛武。

調(diào)用 next(group)缝其,預(yù)激委派生成器 group,此時進(jìn)入 while 循環(huán)徘六,調(diào)用子生成器 average 后内边,在 yield from 表達(dá)式處暫停。內(nèi)層 for 循環(huán)調(diào)用 group.send(value)待锈,直接把值傳給子生成器 average漠其。同時,當(dāng)前的 groupyield from 表達(dá)式處暫停竿音。

內(nèi)層循環(huán)結(jié)束后和屎,group 實例依舊在 yield from 表達(dá)式處暫停。因此春瞬,grouper 函數(shù)定義體中為 results[key] 賦值的語句還沒有執(zhí)行柴信。如果外層 for 循環(huán)的末尾沒有 group.send(None) ,那么 average 子生成器永遠(yuǎn)不會終止快鱼,委派生成器 group 永遠(yuǎn)不會再次激活颠印,因此永遠(yuǎn)不會為 results[key] 賦值纲岭。

外層 for 循環(huán)重新迭代時會新建一個 grouper 實例,然后綁定到 group 變量上线罕。前一個 grouper 實例止潮,以及它創(chuàng)建的尚未終止的 average子生成器實例,被垃圾回收機制回收钞楼。

注意:

  • group.send(None) 以及 average 循環(huán)中的條件判斷是至關(guān)重要的終止條件喇闸。如果不這么做,使用 yield from 調(diào)用這個協(xié)程的生成器會永遠(yuǎn)阻塞询件。
  • 返回的 Result 會成為 grouper 函數(shù)中 yield from 表達(dá)式的值燃乍。
  • grouper 是委派生成器;
  • 這個循環(huán)每次迭代時會新建一個 average 實例宛琅,每個實例都作為協(xié)程使用的生成器對象刻蟹。
  • grouper 發(fā)送的每個值都會經(jīng)由 yield from 處理,通過管道傳給 average 實例嘿辟。group 會在 yield from 表達(dá)式處暫停舆瘪,等待 average 實例處理客戶端發(fā)來的值。average 實例運行完畢后红伦,返回的值綁定到 results[key] 上英古。while 循環(huán)會不斷創(chuàng)建 average 實例,處理更多的值昙读。
  • 把各個 value 傳給 grouper 召调,傳入的值最終到達(dá) averager 函數(shù)中 term = yield 那一行;grouper 永遠(yuǎn)不知道傳入的值是什么蛮浑。
  • None 傳入 grouper 唠叛,導(dǎo)致當(dāng)前的 averager 實例終止,也讓 grouper 繼續(xù)運行陵吸,再創(chuàng)建一個 averager 實例玻墅,處理下一組值。

這個實驗想表名的關(guān)鍵一點是壮虫,如果子生成器不終止澳厢,委派生成器會在 yield from 表達(dá)式處永遠(yuǎn)暫停。如果是這樣囚似,程序不會向前執(zhí)行剩拢,因為 yield from 把控制權(quán)交給客戶代碼(即委派生成器的調(diào)用方)了。

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 異常拋出吗坚。
  • yield from 表達(dá)式的值是子生成器終止時傳給 StopIteration 異常的第一個參數(shù)。

關(guān)于 yield 實現(xiàn)生成器的介紹至此已經(jīng)完成呆万,下一節(jié)我們將使用 asyncio 來實現(xiàn)協(xié)程商源。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谋减,隨后出現(xiàn)的幾起案子牡彻,更是在濱河造成了極大的恐慌,老刑警劉巖出爹,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讨便,死亡現(xiàn)場離奇詭異,居然都是意外死亡以政,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門伴找,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盈蛮,“玉大人,你說我怎么就攤上這事技矮《队” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵衰倦,是天一觀的道長袒炉。 經(jīng)常有香客問我,道長樊零,這世上最難降的妖魔是什么我磁? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮驻襟,結(jié)果婚禮上夺艰,老公的妹妹穿的比我還像新娘。我一直安慰自己沉衣,他們只是感情好郁副,可當(dāng)我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著豌习,像睡著了一般存谎。 火紅的嫁衣襯著肌膚如雪拔疚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天既荚,我揣著相機與錄音稚失,去河邊找鬼。 笑死固以,一個胖子當(dāng)著我的面吹牛墩虹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播憨琳,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诫钓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了篙螟?” 一聲冷哼從身側(cè)響起菌湃,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遍略,沒想到半個月后惧所,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡绪杏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年下愈,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕾久。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡势似,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出僧著,到底是詐尸還是另有隱情履因,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布盹愚,位于F島的核電站栅迄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏皆怕。R本人自食惡果不足惜毅舆,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望端逼。 院中可真熱鬧朗兵,春花似錦、人聲如沸顶滩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盐欺,卻和暖如春赁豆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冗美。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工魔种, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粉洼。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓节预,卻偏偏與公主長得像,于是被迫代替她去往敵國和親属韧。 傳聞我的和親對象是個殘疾皇子安拟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容