【python】協(xié)程:協(xié)程與生成器的對(duì)比叭喜、激活協(xié)程贺拣、終止協(xié)程和異常處理

基于生成器的協(xié)程

生成器可以作為協(xié)程(coroutine)使用,稱為 "基于生成器的協(xié)程"捂蕴。
協(xié)程和生成器類似譬涡,都是定義體中包含 yield 關(guān)鍵字的函數(shù)。但它們也有本質(zhì)區(qū)別:

  • 生成器用于生成 供迭代的數(shù)據(jù)啥辨,next()方法只允許調(diào)用方從生成器中獲取數(shù)據(jù)涡匀;
  • 而協(xié)程與迭代無(wú)關(guān),協(xié)程是數(shù)據(jù)的消費(fèi)者溉知,調(diào)用方會(huì)把數(shù)據(jù)推送給協(xié)程陨瘩。

【知識(shí)拓展】:
2001年,Python 2.2 通過(guò)了 PEP 255 -- Simple Generators 级乍,引入了yield 關(guān)鍵字實(shí)現(xiàn)了生成器函數(shù)舌劳,yield 包含 產(chǎn)出 和 讓步兩個(gè)含義: 生成器中yield x這行代碼會(huì) 產(chǎn)出 一個(gè)值,提供給 next(...) 的調(diào)用方玫荣; 此外,還會(huì)作出讓步甚淡,暫停執(zhí)行生成器,讓調(diào)用方繼續(xù)工作捅厂,直到需要使用另一個(gè)值時(shí)再調(diào)用 next(...);

2005年贯卦,Python 2.5 通過(guò)了 PEP 342 - "Coroutines via Enhanced Generators"资柔,給生成器增加了.send()、.throw()和.close()方法撵割,第一次實(shí)現(xiàn)了基于生成器的協(xié)程函數(shù)(generator-based coroutines)


協(xié)程的簡(jiǎn)單使用
  • (coroutine function):協(xié)程函數(shù)贿堰;
  • (coroutine object):協(xié)程對(duì)象;

生成器的調(diào)用方可以使用.send(...)方法發(fā)送數(shù)據(jù)睁枕,發(fā)送的數(shù)據(jù)會(huì)成為生成器函數(shù)中yield表達(dá)式的值官边。因此,生成器可以作為協(xié)程使用外遇。協(xié)程是指一個(gè)過(guò)程,這個(gè)過(guò)程與調(diào)用方協(xié)作契吉,產(chǎn)出由調(diào)用方提供的值跳仿。

def simple_coroutine():
    print("->coroutine started")
    x = yield # ①
    print("-> coroutine received:", x)

my_coro = simple_coroutine()
next(my_coro)  # ②
my_coro.send(42) # ③

OUTPUT:

->coroutine started
Traceback (most recent call last):
-> coroutine received: 42
  File "C:/Users/admin/Desktop/untitled1/6-30/coroutine.6-30.py", line 129, in <module>
    my_coro.send(42) # ③
StopIteration

【解釋一下】:

  • ① yield在表達(dá)式中使用;如果協(xié)程只需從客戶那里接收數(shù)據(jù)捐晶,那么產(chǎn)出的值是None——這個(gè)值是隱式指定的菲语,因?yàn)閥ield關(guān)鍵字右邊沒(méi)有表達(dá)式。

  • ② 首先要調(diào)用next(...)函數(shù)惑灵,因?yàn)樯善鬟€沒(méi)啟動(dòng)山上,沒(méi)在yield語(yǔ)句處暫停,所以一開(kāi)始無(wú)法發(fā)送數(shù)據(jù)英支。

  • ③ 調(diào)用這個(gè)方法后佩憾,協(xié)程定義體中的yield表達(dá)式會(huì)計(jì)算出42;現(xiàn)在干花,協(xié)程會(huì)恢復(fù)妄帘,一直運(yùn)行到下一個(gè)yield表達(dá)式,或者終止池凄。


協(xié)程與生成器的對(duì)比
  • (生成器)generators are data producers:

生成器用于生成供迭代的數(shù)據(jù)抡驼,next()方法只允許調(diào)用方從生成器中獲取數(shù)據(jù);

  • (協(xié)程)coroutines are data consumers:

協(xié)程是數(shù)據(jù)的消費(fèi)者肿仑,調(diào)用方會(huì)把數(shù)據(jù)推送給協(xié)程致盟。send()方法允許調(diào)用方和協(xié)程之間雙向交換數(shù)據(jù)。

【注意】: 協(xié)程也可以產(chǎn)出值尤慰,但這與迭代無(wú)關(guān)馏锡;


協(xié)程的狀態(tài)
  • 'GEN_CREATED': 等待開(kāi)始執(zhí)行;

  • 'GEN_RUNNING': 正在被解釋器執(zhí)行割择。只有在多線程應(yīng)用中才能看到這個(gè)狀態(tài)眷篇;

  • 'GEN_SUSPENDED': 在yield表達(dá)式處暫停;

  • 'GEN_CLOSED': 執(zhí)行結(jié)束荔泳;

【注釋】:可以使用 inspect.getgeneratorstate(...) 函數(shù)查看協(xié)程的當(dāng)前狀態(tài)

舉例來(lái)看:

def simple_coroutine():
    print("->coroutine started")
    x = yield
    print(getgeneratorstate(my_coro))
    print("-> coroutine received:", x)

my_coro = simple_coroutine()
print(getgeneratorstate(my_coro))
next(my_coro)
print(getgeneratorstate(my_coro))
my_coro.send(27)
print(getgeneratorstate(my_coro))

Output:

GEN_CREATED
Traceback (most recent call last):
->coroutine started
GEN_SUSPENDED
  File "C:/Users/admin......(嘻嘻,保密)......py", line 144, in <module>
GEN_RUNNING
    my_coro.send(27)
-> coroutine received: 27
StopIteration

【注意點(diǎn)】

  • 因?yàn)閟end方法的參數(shù)會(huì)成為暫停的yield表達(dá)式的值蕉饼,所以虐杯,僅當(dāng)協(xié)程處于暫停狀態(tài)時(shí)才能調(diào)用send方法。

  • 如果給未激活的協(xié)程對(duì)象發(fā)送None以外的值昧港,會(huì)引發(fā)錯(cuò)誤 (避免小錯(cuò)誤哦)擎椰。


激活協(xié)程的方式
  • next(...)方法;
  • my_coro.send(None)创肥;

小二...給朕上代碼:

def simple_coro2(a):
    print('->Started :a = ' ,a)
    b = yield a
    print('->Started :b = ' ,b)
    c = yield a + b
    print('->Received: c=',c)

my_coro2 = simple_coro2(14)
next(my_coro2)
rs1 = my_coro2.send(28)
print(rs1)
rs2 = my_coro2.send(99)
print(rs2)

計(jì)算移動(dòng)平均值

def averager():
    total = 0.0
    count = 0
    average = 0.0

    while True:
        term = yield average
        total += term
        count += 1
        average = total /count

cor_avg = averager()
next(cor_avg)
cor_avg.send(20)
cor_avg.send(5)
cor_avg.send(27)
----
輸出:
20.0
12.5
17.333333333333332

【知識(shí)拓展】:關(guān)于wraps(寫(xiě)的很詳細(xì)):
https://blog.csdn.net/hqzxsc2006/article/details/50337865


Python裝飾器

在實(shí)現(xiàn)的時(shí)候达舒,被裝飾后的函數(shù)其實(shí)已經(jīng)是另外一個(gè)函數(shù)了(函數(shù)名等函數(shù)屬性會(huì)發(fā)生改變),為了不影響叹侄,Python的functools包中提供了一個(gè)叫wraps的decorator來(lái)消除這樣的副作用巩搏。

寫(xiě)一個(gè)decorator的時(shí)候,最好在實(shí)現(xiàn)之前加上functools的wrap趾代,它能保留原有函數(shù)的名稱和docstring贯底。

#不加wraps
def my_decorator(func):
    
    def wrapper(*args, **kwargs):
        """decorator"""
        print("Calling decorated function...")
        return func(*args,**kwargs)

    return wrapper

@my_decorator
def example():
    """Docstring"""
    print('Called example function')
    
print(example.__name__,example.__doc__)

#輸出:
wrapper decorator


#加wraps

from functools import wraps
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """decorator"""
        print("Calling decorated function...")
        return func(*args,**kwargs)

    return wrapper

@my_decorator
def example():
    """Docstring"""
    print('Called example function')
    
print(example.__name__,example.__doc__)

Output:
example Docstring


【預(yù)激協(xié)程】
調(diào)用send()方法之前,需要先調(diào)用next(my_coro)方法進(jìn)行激活撒强。有時(shí)候?yàn)榱撕?jiǎn)化協(xié)程的用法禽捆,有時(shí)會(huì)使用一個(gè)預(yù)激裝飾器。

一起來(lái)看一個(gè)例子:

from functools import wraps

def coroutine(func):
    @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 = None

    while True:
        term = yield average
        total += term
        count += 1
        average = total / count

from inspect import getgeneratorstate
coro_avg = averager()
print(getgeneratorstate(coro_avg))
print(coro_avg.send(10))
print(coro_avg.send(30))

Output:

GEN_SUSPENDED
10.0
20.0

【解釋】:如果使用yield from 語(yǔ)法飘哨,會(huì)自動(dòng)預(yù)激胚想。

asyncio.coroutine裝飾器不會(huì)預(yù)激活協(xié)程,因此能兼容yield from 句法芽隆。


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

協(xié)程中未處理的異常會(huì)向上冒泡浊服,傳給next函數(shù)或send方法的調(diào)用方(觸發(fā)協(xié)程的對(duì)象)。

  • 由于第三行發(fā)送的不是數(shù)據(jù)摆马,導(dǎo)致協(xié)程內(nèi)部拋出異常臼闻;
  • 由于協(xié)程內(nèi)部沒(méi)有處理異常,協(xié)程會(huì)終止囤采。如果試圖重新激活協(xié)程述呐,會(huì)拋出StopIteration異常;
異常處理的兩種方式
  • generator.throw(exec_type[,exc_value[,traceback]])
  • 會(huì)使生成器在暫停的yield表達(dá)式處拋出指定的異常蕉毯;

  • 如果生成器處理了拋出的異常乓搬,代碼會(huì)向前執(zhí)行到下一個(gè)yield表達(dá)式,而產(chǎn)出的值會(huì)成為調(diào)用generator.throw方法得到的返回值代虾;

  • 如果生成器沒(méi)有處理拋出的異常进肯,異常會(huì)向上冒泡,傳到調(diào)用方的上下文中棉磨;


  • generator.close()
  • 致使生成器在暫停的yield表達(dá)式處拋出GeneratorExit異常(正常終止協(xié)程)江掩。

  • 如果生成器沒(méi)有處理這個(gè)異常,或者拋出了StopIteration異常(通常是指運(yùn)行到結(jié)尾),調(diào)用方不會(huì)報(bào)錯(cuò)环形;

  • 如果收到GeneratorExit異常策泣,生成器一定不能產(chǎn)出值,否則解釋器會(huì)拋出RuntimeError異常抬吟。生成器拋出的其他異常會(huì)向上冒泡萨咕,傳給調(diào)用方。

【注意1】如果收到GeneratorExit異常火本,生成器一定不能產(chǎn)出值危队,否則解釋器會(huì)拋出RuntimeError異常;
生成器拋出的其他異常會(huì)向上冒泡钙畔,傳遞給調(diào)用方茫陆;
【注意2】但是,如果傳入?yún)f(xié)程的異常沒(méi)有處理刃鳄,協(xié)程會(huì)終止盅弛,即狀態(tài)變成 'GEN_CLOSED':

呈上一份代碼:

class DemoException(Exception):
    pass

def demo_exc_handing():
    print('==>coroutine strated')

    while True:
        try:
            x = yield       # 產(chǎn)生異常!
        except DemoException:  # 特別處理 DemoException 異常
            print('==> DemoException handled.Continuing...')
        else:         # 如果沒(méi)有異常叔锐,那么顯示接收到的值
            print('==>coroutine received:{!r}'.format(x))
        finally:
            print('==>Ending...')
exc_coro = demo_exc_handing()
next(exc_coro)
exc_coro.send(11)
exc_coro.send(22)

print(inspect.getgeneratorstate(exc_coro))

輸出:

==>Ending...
==>coroutine strated
==>coroutine received:11
==>Ending...
==>coroutine received:22
==>Ending...
GEN_SUSPENDED
基于jupyter-notebook

【解釋】如果把DemoException異常傳入demo_finally協(xié)程,它會(huì)處理见秽,然后繼續(xù)運(yùn)行

exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(11)
exc_coro.send(22)
# 將 DemoException 傳入
exc_coro.throw(DemoException)
from inspect import getgeneratorstate
print(getgeneratorstate(exc_coro))

仔細(xì)看上述兩者的區(qū)別
【下一步】:如果傳入的異常沒(méi)有處理愉烙,協(xié)程會(huì)立即停止,變成'GEN_CLOSED'狀態(tài)解取。
例如:

exc_coro = demo_exc_handling()
next(exc_coro)
exc_coro.send(11)
exc_coro.send(22)
# 將 DemoException 傳入
exc_coro.throw(ZeroDivisionError)
from inspect import getgeneratorstate
print(getgeneratorstate(exc_coro))

【技能強(qiáng)化】:如果協(xié)程必須做一些清理工作步责,則可以在協(xié)程體中放入try/finally 代碼塊
例如:

def demo_finally():
    print('-> coroutine started')
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print('*** DemoException handled. Continuing...')
            else:
                print('-> coroutine received: {!r}'.format(x))
    finally:
        print("->coroutine ending...")

demo_coro = demo_finally();
next(demo_coro)
demo_coro.send(11)
demo_coro.send(ZeroDivisionError)

讓協(xié)程return值

Python 3.3以后,允許在協(xié)程中有return expr表達(dá)式禀苦,如果執(zhí)行到該語(yǔ)句蔓肯,則協(xié)程運(yùn)行結(jié)束。同時(shí)會(huì)拋出StopIteration異常振乏,而返回值就在該異常對(duì)象的value屬性上蔗包。
【重點(diǎn)】如果協(xié)程中有return語(yǔ)句,則返回return后面的表達(dá)式的值慧邮,否則返回None
有些協(xié)程在被激活后调限,每次驅(qū)動(dòng)(drive)協(xié)程時(shí),不會(huì)產(chǎn)出值误澳,而是在最后(協(xié)程正常終止時(shí))返回一個(gè)值(通常是某種累加值)

還是上述講過(guò)的例子耻矮,在這里會(huì)返回平均值:

from collections import namedtuple
Result = namedtuple('Result','count average')

def averager():
    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)

coro_avg = averager();
next(coro_avg)
coro_avg.send(10)
coro_avg.send(30)
coro_avg.send(20)
coro_avg.send(None)

Output:

Traceback (most recent call last):
  File "C:/Users/...(保密,嘻嘻)...py", line 25, in <module>
    coro_avg.send(None)
StopIteration: Result(count=3, average=20.0)

生成器對(duì)象會(huì)拋出StopIteration異常忆谓。異常對(duì)象的value屬性保存著返回的值.....
【yield from結(jié)構(gòu)】:在內(nèi)部自動(dòng)捕獲StopIteration異常裆装。對(duì)yield from結(jié)構(gòu)來(lái)說(shuō),解釋器不僅會(huì)捕獲StopIteration異常,還會(huì)把value屬性的值變成yield from表達(dá)式的值哨免。

使用協(xié)程的優(yōu)點(diǎn)
  • 協(xié)程最大的優(yōu)勢(shì)就是協(xié)程極高的執(zhí)行效率茎活。子程序切換由程序自身控制,因此铁瞒,沒(méi)有線程切換的開(kāi)銷(xiāo)妙色;
  • 不需要多線程的鎖機(jī)制,阻塞自動(dòng)切換慧耍;
  • 程即是生成器對(duì)象的yield和send方法的配合使用身辨;

【關(guān)于python中的 yield from 句法】:http://www.reibang.com/p/6c6761a407c5


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市芍碧,隨后出現(xiàn)的幾起案子煌珊,更是在濱河造成了極大的恐慌,老刑警劉巖泌豆,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件定庵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡踪危,警方通過(guò)查閱死者的電腦和手機(jī)蔬浙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贞远,“玉大人畴博,你說(shuō)我怎么就攤上這事±吨伲” “怎么了俱病?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)袱结。 經(jīng)常有香客問(wèn)我亮隙,道長(zhǎng),這世上最難降的妖魔是什么垢夹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任溢吻,我火速辦了婚禮,結(jié)果婚禮上棚饵,老公的妹妹穿的比我還像新娘煤裙。我一直安慰自己,他們只是感情好噪漾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布硼砰。 她就那樣靜靜地躺著,像睡著了一般欣硼。 火紅的嫁衣襯著肌膚如雪题翰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音豹障,去河邊找鬼冯事。 笑死,一個(gè)胖子當(dāng)著我的面吹牛血公,可吹牛的內(nèi)容都是我干的昵仅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼累魔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼摔笤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起垦写,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吕世,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后梯投,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體命辖,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年分蓖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尔艇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡么鹤,死狀恐怖漓帚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情午磁,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布毡们,位于F島的核電站迅皇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏衙熔。R本人自食惡果不足惜登颓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望红氯。 院中可真熱鬧框咙,春花似錦、人聲如沸痢甘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)塞栅。三九已至者铜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背作烟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工愉粤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拿撩。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓衣厘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親压恒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子影暴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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

  • 目錄:一、基于生成器的協(xié)程二涎显、協(xié)程狀態(tài)三坤检、協(xié)程預(yù)激裝飾器四、終止協(xié)程和異常處理五期吓、協(xié)程返回值六早歇、yield fro...
    Recalcitrant閱讀 388評(píng)論 0 0
  • 從語(yǔ)法上來(lái)看,協(xié)程和生成器類似讨勤,都是定義體中包含yield關(guān)鍵字的函數(shù)箭跳。yield在協(xié)程中的用法: 在協(xié)程中yie...
    忘了呼吸的那只貓閱讀 333評(píng)論 0 2
  • 協(xié)程 to yield 含義:產(chǎn)出和讓步。 yield item這行代碼會(huì)產(chǎn)出一個(gè)值潭千,提供給next(...)的調(diào)...
    風(fēng)果常識(shí)閱讀 943評(píng)論 0 3
  • 從語(yǔ)法上來(lái)看谱姓,協(xié)程和生成器類似,都是定義體中包含yield關(guān)鍵字的函數(shù)刨晴。yield在協(xié)程中的用法:在協(xié)程中yiel...
    JokerW閱讀 1,803評(píng)論 0 0
  • 001 安全感和鐵飯碗不是穩(wěn)定地工作帶來(lái)的屉来,而是自己的本事,如果碰見(jiàn)政策洗牌狈癞,時(shí)代換血茄靠,要有對(duì)自我負(fù)責(zé)的能力。 0...
    金一鶴閱讀 170評(píng)論 0 0