python實現協程(二)

一. 預激活協程的裝飾器

       調用協程函數後,返回的是一個協程對象,函數本身並不會執行。所以在調用send方法前,必須使用next()或send(None)來預激活協程函數,是協程函數執行到第一個yield表達式,處於暫停狀態。爲了簡化操作,下面我們定義一個預激活的裝飾器:

from functools import wraps

def coroutine(func):
    """裝飾器:向前執行到第一個yield表達式,預激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

        我們定義的裝飾中,把被裝飾的生成器函數替換成這裏的primer函數,調用primer函數時,返回預激活後的生成器。接下來使用@coroutine裝飾器裝飾average()協程函數。

運行結果:調用上述模塊中定義的average()函數創建一個生成器對象,因爲在coroutine裝飾器的primer函數中已經預激活了生成器,所以getgeneratorstate(coro_ava)返回“GEN_SUSPENDED”狀態,因此協程已經準備好,可以接收值了。

 

        很多框架都提供了處理協程的特殊裝飾器,不過不是所有裝飾器都用於預激協程,有些會提供其它服務,例如勾入事件循環。 比如說,異步網絡庫Tornado提供了tornado.gen裝飾器。

        另外,使用yield from句法調用協程時,會自動預激。python標準庫裏的asyncio.coroutine裝飾器不會預激協程,因此能兼容yield from句法。

二. 終止協程和異常處理

        協程中未處理的異常會向上冒泡,傳給next()函數或send()方法的調用方(即觸發協程的對象)。因此,未處理的異常會導致協程終止。以上面的coro_ava協程實例爲例:

        由於發送給average()函數的值不是數字,因此協程內有異常拋出。由於在協程內沒有處理異常,協程會終止。如果視圖重新激活協程,會拋出StopIteration異常。

        上述示例暗示了終止協程的一種方式,發送一個異常給協程。python2.5開始,客戶端代碼可以在生成器對象上調用2個方法,顯示地把異常發給協程:

  1. generator.throw(exc_type[, exc_value[, traceback]]):使生成器在暫停的yield表達式處拋出指定異常。如果生成器處理了拋出的異常,代碼會向前執行到下一個yield表達式,而產出的值會成爲調用generator.throw方法得到的返回值。如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調用方的上下文中。
  2. generator.close():使生成器在暫停的yield表達式處拋出GeneratorExit異常。如果生成器沒有處理這個異常,或者拋出了StopIterator異常,調用方不會報錯。生成器拋出的其它異常會向上冒泡,傳給調用方。

示例代碼  學習在協程中處理異常

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終止協程,不會有異常

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

演示3 如果無法處理傳入的異常,協程會終止

         如果不管協程如何結束都想做些清理工作,要把協程定義體中相關的代碼放入try/finally塊中。下面我們改進上述demo_exc_handler協程函數的定義,使用try/finally在協程終止時執行操作:

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在協程終止時執行操作

        python 3.3引入yield from結構的主要原因之一與把異常傳入嵌套的協程有關。另一個原因是讓協程更方便的返回值,下節將介紹讓協程返回值以及yield from的用法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章