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

一. 預(yù)激活協(xié)程的裝飾器

調(diào)用協(xié)程函數(shù)后,返回的是一個協(xié)程對象伙单,函數(shù)本身并不會執(zhí)行胯杭。所以在調(diào)用 send 方法前,必須使用 next()send(None) 來預(yù)激活協(xié)程函數(shù)膜宋,使協(xié)程函數(shù)執(zhí)行到第一個 yield 表達式窿侈,處于暫停狀態(tài)。

為了簡化操作秋茫,下面我們定義一個預(yù)激活的裝飾器:

from functools import wraps
 
def coroutine(func):
    """裝飾器:向前執(zhí)行到第一個yield表達式史简,預(yù)激func"""
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return primer
 
 
@coroutine
def average():
    total = 0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count

我們定義的裝飾中,把被裝飾的生成器函數(shù)替換成這里的 primer 函數(shù)肛著,調(diào)用 primer 函數(shù)時圆兵,返回預(yù)激活后的生成器。接下來使用 @coroutine 裝飾器裝飾 average() 協(xié)程函數(shù)枢贿。

運行結(jié)果:調(diào)用上述模塊中定義的 average() 函數(shù)創(chuàng)建一個生成器對象殉农,因為在 coroutine 裝飾器的 primer 函數(shù)中已經(jīng)預(yù)激活了生成器,所以 getgeneratorstate(coro_ava) 返回 GEN_SUSPENDED 狀態(tài)局荚,因此協(xié)程已經(jīng)準備好超凳,可以接收值了。

很多框架都提供了處理協(xié)程的特殊裝飾器耀态,不過不是所有裝飾器都用于預(yù)激協(xié)程轮傍,有些會提供其它服務(wù),例如勾入事件循環(huán)首装。 比如說创夜,異步網(wǎng)絡(luò)庫 Tornado 提供了 tornado.gen 裝飾器。

另外仙逻,使用 yield from 句法調(diào)用協(xié)程時驰吓,會自動預(yù)激。python 標準庫里的 asyncio.coroutine 裝飾器不會預(yù)激協(xié)程系奉,因此能兼容 yield from 句法檬贰。

二. 終止協(xié)程和異常處理

協(xié)程中未處理的異常會向上冒泡,傳給 next() 函數(shù)或 send() 方法的調(diào)用方(即觸發(fā)協(xié)程的對象)喜最。因此偎蘸,未處理的異常會導致協(xié)程終止。以上面的 coro_ava 協(xié)程實例為例:

由于發(fā)送給 average() 函數(shù)的值不是數(shù)字,因此協(xié)程內(nèi)有異常拋出迷雪。由于在協(xié)程內(nèi)沒有處理異常限书,協(xié)程會終止。如果視圖重新激活協(xié)程章咧,會拋出 StopIteration 異常倦西。

上述示例暗示了終止協(xié)程的一種方式,發(fā)送一個異常給協(xié)程赁严。Python2.5 開始扰柠,客戶端代碼可以在生成器對象上調(diào)用 2 個方法,顯示地把異常發(fā)給協(xié)程:

  • generator.throw(exc_type[, exc_value[, traceback]]):使生成器在暫停的 yield 表達式處拋出指定異常疼约。如果生成器處理了拋出的異常卤档,代碼會向前執(zhí)行到下一個 yield 表達式,而產(chǎn)出的值會成為調(diào)用 generator.throw 方法得到的返回值程剥。如果生成器沒有處理拋出的異常劝枣,異常會向上冒泡,傳到調(diào)用方的上下文中织鲸。

  • generator.close():使生成器在暫停的 yield 表達式處拋出 GeneratorExit 異常舔腾。如果生成器沒有處理這個異常,或者拋出了 StopIterator 異常搂擦,調(diào)用方不會報錯稳诚。生成器拋出的其它異常會向上冒泡,傳給調(diào)用方瀑踢。

示例代碼 在協(xié)程中處理異常

class DemoException(Exception):
    """為這次演示定義的異常類型"""
 
 
def demo_exc_handler():
    print('-> coroutine started')
    while True:
        try:
            x = yield
        except DemoException:
            print('*** DemoException handled. Continue ***')
        else:
            print(f'-> coroutine received:{x}')
    raise RuntimeError('This line should never run.')

演示 1 close 終止協(xié)程扳还,不會有異常

演示 2 如果把 DemoException 異常傳入 demo_exc_handler ,不會導致協(xié)程終止

演示 3 如果無法處理傳入的異常丘损,協(xié)程會終止

如果不管協(xié)程如何結(jié)束都想做些清理工作普办,要把協(xié)程定義體中相關(guān)的代碼放入 try / finally 塊中工扎。

下面我們改進上述 demo_exc_handler 協(xié)程函數(shù)的定義徘钥,使用 try / finally 在協(xié)程終止時執(zhí)行操作:

class DemoException(Exception):
    """為這次演示定義的異常類型"""
 
 
def demo_finally():
    print('-> coroutine started')
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print('*** DemoException handled. Continue ***')
            else:
                print(f'-> coroutine received:{x}')
    finally:
        print('-> coroutine ending')

演示 使用 try / finally 在協(xié)程終止時執(zhí)行操作

Python 3.3 引入 yield from 結(jié)構(gòu)的主要原因之一與把異常傳入嵌套的協(xié)程有關(guān)。另一個原因是讓協(xié)程更方便的返回值肢娘,下節(jié)將介紹讓協(xié)程返回值以及 yield from 的用法呈础。

?著作權(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
  • 文/潘曉璐 我一進店門巨税,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人粉臊,你說我怎么就攤上這事草添。” “怎么了扼仲?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵远寸,是天一觀的道長。 經(jīng)常有香客問我屠凶,道長驰后,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任矗愧,我火速辦了婚禮倡怎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贱枣。我一直安慰自己监署,他們只是感情好,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布纽哥。 她就那樣靜靜地躺著钠乏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪春塌。 梳的紋絲不亂的頭發(fā)上晓避,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音只壳,去河邊找鬼俏拱。 笑死,一個胖子當著我的面吹牛吼句,可吹牛的內(nèi)容都是我干的锅必。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惕艳,長吁一口氣:“原來是場噩夢啊……” “哼搞隐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起远搪,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤劣纲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谁鳍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體癞季,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡劫瞳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绷柒。 大學時的朋友給我發(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
  • 正文 我出身青樓抡医,卻偏偏與公主長得像躲因,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子魂拦,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

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