python-復(fù)盤-協(xié)程yield from 徹底詳解yield from/asyncio異步io/協(xié)程最簡例子

Python3.3版本的PEP 380中添加了yield from語法标锄,允許一個generator生成器將其部分操作委派給另一個生成器。其產(chǎn)生的主要動力在于使生成器能夠很容易分為多個擁有sendthrow方法的子生成器茁计,像一個大函數(shù)可以分為多個子函數(shù)一樣簡單。Python的生成器是協(xié)程coroutine的一種形式谓松,但它的局限性在于只能向它的直接調(diào)用者yield值星压。這意味著那些包含yield的代碼不能想其他代碼那樣被分離出來放到一個單獨的函數(shù)中。這也正是yield from要解決的鬼譬。
雖然【yield from主要設(shè)計用來向子生成器委派操作任務(wù)】娜膘,但yield from可以向任意的迭代器委派操作;
對于簡單的迭代器优质,yield from iterable本質(zhì)上等于for item in iterable: yield item的縮寫版竣贪,如下所示:

>>> def g(x):
...     yield from range(x, 0, -1)
...     yield from range(x)
...
>>> list(g(5))
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

然而,不同于普通的循環(huán)巩螃,yield from允許子生成器直接從調(diào)用者接收其發(fā)送的信息或者拋出調(diào)用時遇到的異常演怎,并且返回給委派生產(chǎn)器一個值,如下所示:

>>> def accumulate():    # 子生成器避乏,將傳進的非None值累加爷耀,傳進的值若為None,則返回累加結(jié)果
...     tally = 0        # 由于for循環(huán)被分配到子生成器去執(zhí)行拍皮,tally不斷的累加
...     while 1:
...         next = yield
...         if next is None:
...             return tally
...         tally += next
...
>>> def gather_tallies(tallies):    # 外部生成器歹叮,將累加操作任務(wù)委托給子生成器
...     while 1:
...         tally = yield from accumulate()   # 這里for 會先執(zhí)行完 accumulate()后再from,把item 給yield跑杭,注意??程序執(zhí)行順序
...         tallies.append(tally)   #  由于子生成器累加后才把值append過來,所以最后結(jié)果是[6,10]
...
>>> tallies = []
>>> acc = gather_tallies(tallies)
>>> next(acc)    # 使累加生成器準備好接收傳入值
>>> for i in range(4):   # 這個for循環(huán)針對的是acc,按理說for循環(huán)作用于外部生成器咆耿,但實際上德谅,for 作用于子生成器,也就是上面說的 yield from 主要設(shè)計用來向子生成器委派操作任務(wù)的
...     acc.send(i)
...
>>> acc.send(None)    # 結(jié)束第一次累加
>>> for i in range(5):
...     acc.send(i)
...
>>> acc.send(None)    # 結(jié)束第二次累加
>>> tallies    # 輸出最終結(jié)果
[6, 10]  

總結(jié)如下:

  1. 迭代器(即可指子生成器)產(chǎn)生的值直接返還給調(diào)用者
  2. 任何使用send()方法發(fā)給委派生產(chǎn)器(即外部生產(chǎn)器)的值被直接傳遞給迭代器萨螺。如果send值是None窄做,則調(diào)用迭代器next()方法;如果不為None屑迂,則調(diào)用迭代器的send()方法浸策。如果對迭代器的調(diào)用產(chǎn)生StopIteration異常,委派生產(chǎn)器恢復(fù)繼續(xù)執(zhí)行yield from后面的語句惹盼;若迭代器產(chǎn)生其他任何異常庸汗,則都傳遞給委派生產(chǎn)器。
  3. 除了GeneratorExit 異常外的其他拋給委派生產(chǎn)器的異常手报,將會被傳遞到迭代器的throw()方法蚯舱。如果迭代器throw()調(diào)用產(chǎn)生了StopIteration異常,委派生產(chǎn)器恢復(fù)并繼續(xù)執(zhí)行掩蛤,其他異常則傳遞給委派生產(chǎn)器枉昏。
  4. 如果GeneratorExit異常被拋給委派生產(chǎn)器,或者委派生產(chǎn)器的close()方法被調(diào)用揍鸟,如果迭代器有close()的話也將被調(diào)用兄裂。如果close()調(diào)用產(chǎn)生異常,異常將傳遞給委派生產(chǎn)器阳藻。否則晰奖,委派生產(chǎn)器將拋出GeneratorExit 異常。
  5. 當(dāng)?shù)鹘Y(jié)束并拋出異常時腥泥,yield from表達式的值是其StopIteration 異常中的第一個參數(shù)匾南。
  6. 一個生成器中的return expr語句將會從生成器退出并拋出 StopIteration(expr)異常。

stackoverflow上的經(jīng)典解釋

將yield from視為提供了一個調(diào)用者和子生成器之間的透明的雙向通道蛔外。包括從子生成器獲取數(shù)據(jù)以及向子生成器傳送數(shù)據(jù)蛆楞。

1. 利用yield from從生成器讀取數(shù)據(jù)

def reader():
    # 模擬從文件讀取數(shù)據(jù)的生成器
    for i in range(4):
        yield '<< %s' % i

def reader_wrapper(g):
    # 循環(huán)迭代從reader產(chǎn)生的數(shù)據(jù) 
    for v in g:
        yield v

wrap = reader_wrapper(reader())
for i in wrap:
    print(i)

# 結(jié)果:
<< 0
<< 1
<< 2
<< 3

我們可以用yield from語句替代reader_wrapper(g)函數(shù)中的循環(huán),如下:

def reader_wrapper(g):
    yield from g

2.利用yield from語句向生成器(協(xié)程)傳送數(shù)據(jù)

首先創(chuàng)建一個生成器writer夹厌,接收傳送給它的數(shù)據(jù)豹爹,并寫進套接字,文件等矛纹;

def writer():
    # 讀取send傳進的數(shù)據(jù)帅戒,并模擬寫進套接字或文件
    while True:
        w =  yield    # w接收send傳進的數(shù)據(jù)
        print('>> ', w)

現(xiàn)在的問題是,包裝器函數(shù)如何傳送數(shù)據(jù)給writer函數(shù),使得傳遞給包裝器的數(shù)據(jù)都能夠顯示地傳遞給writer函數(shù)逻住?钟哥!
即我們期待得到如下結(jié)果:

def writer_wrapper(coro):
    # TBD
    pass

w = writer()
wrap = writer_wrapper(w)
wrap.send(None)  # 生成器準備好接收數(shù)據(jù)
for i in range(4):
    wrap.send(i)

# 期望結(jié)果:
>>  0
>>  1
>>  2
>>  3

我們可以用yield from語句替代reader_wrapper(g)函數(shù)中的循環(huán),如下:

def reader_wrapper(g):
    yield from g

效果一樣瞎访,且代碼更簡潔有沒有腻贰。

2.利用yield from語句向生成器(協(xié)程)傳送數(shù)據(jù)

首先創(chuàng)建一個生成器writer,接收傳送給它的數(shù)據(jù)扒秸,并寫進套接字播演,文件等;

def writer():
    # 讀取send傳進的數(shù)據(jù)伴奥,并模擬寫進套接字或文件
    while True:
        w = (yield)    # w接收send傳進的數(shù)據(jù)
        print('>> ', w)

現(xiàn)在的問題是写烤,包裝器函數(shù)如何傳送數(shù)據(jù)給writer函數(shù),使得傳遞給包裝器的數(shù)據(jù)都能夠顯示地傳遞給writer函數(shù)拾徙?洲炊!

即我們期待得到如下結(jié)果:

def writer_wrapper(coro):
    # TBD
    pass

w = writer()
wrap = writer_wrapper(w)
wrap.send(None)  # 生成器準備好接收數(shù)據(jù)
for i in range(4):
    wrap.send(i)

# 期望結(jié)果:
>>  0
>>  1
>>  2
>>  3

很顯然,包裝區(qū)需要接收數(shù)據(jù)并顯示傳遞給生成器尼啡,并且需要處理for循環(huán)耗盡是生成器產(chǎn)生的StopIteration異常暂衡,顯然包裝器只用for循環(huán)已經(jīng)不能滿足需求,滿足情況的一般版本如下:

def writer_wrapper(coro1):
    coro1.send(None)  # 生成器準備好接收數(shù)據(jù)
    while True:
        try:
            x = (yield)  # x接收send傳進的數(shù)據(jù)
            coro1.send(x)  # 然后將x在send給writer子生成器
        except StopIteration:    # 處理子生成器返回的異常
            pass

包裝器也是個生成器崖瞭,上面所有復(fù)雜的寫法也可以用yield from替換:

def writer_wrapper(coro2):
    yield from coro2

一下子少了好多代碼狂巢,是不是見證了奇跡!

3. 利用yield from向生成器傳送數(shù)據(jù)–處理異常

更進一步书聚,如果我們的子生成器即writer需要處理異常該怎么辦唧领?假設(shè)writer需要處理SpamException異常,遇到這個異常打印***雌续,代碼如下:

class SpamException(Exception):
    pass

def writer():
    while True:
        try:
            w = (yield)
        except SpamException:
            print('***')
        else:
            print('>> ', w)

如果使用上一個一般版本的包裝器writer_wrapper(coro1)斩个,會有什么結(jié)果?試驗如下:

w = writer()
wrap = writer_wrapper(w)
wrap.send(None)  # "prime" the coroutine
for i in [0, 1, 2, 'spam', 4]:
    if i == 'spam':
        wrap.throw(SpamException)
    else:
        wrap.send(i)

# 期望結(jié)果:
>>  0
>>  1
>>  2
***
>>  4

# 實際結(jié)果:
>>  0
>>  1
>>  2
Traceback (most recent call last):
  File ... in <module>
    wrap.throw(SpamException)
  File ... in writer_wrapper
    x = (yield)
__main__.SpamException

可以看出西雀,這行不通,因為x = (yield)語句僅能夠引發(fā)異常歉摧,然后停止運行艇肴。我們可以手工在包裝器writer_wrapper(coro1)中添加異常處理,并傳遞或者拋出異常給子生成器writer叁温,代碼如下:

def writer_wrapper(coro1):
    # 手工處理異常被拋給子生成器
    coro1.send(None)    # 生成器準備好接收數(shù)據(jù)
    while True:
        try:
            try:
                x = (yield)
            except Exception as e:   # 捕獲異常
                coro1.throw(e)
            else:
                coro1.send(x)
        except StopIteration:
            pass

同樣的再悼,這一堆復(fù)雜的代碼,也可以用yield from語句替換膝但,并且功能完全一樣3寰拧!!

def writer_wrapper(coro):
    yield from coro

重要的代碼貼三遍莺奸!三遍丑孩!三遍!

看到這里灭贷,大概能理解yield from顯示處理傳值給子生成器以及拋出異常給子生成器的意思了吧温学。
當(dāng)然yield from不僅有這兩個處理情況,還有之前我們提到的:外部生成器關(guān)閉甚疟,子生成器也會關(guān)閉仗岖;子生成器返回一個值得情況(上文第二個代碼例子),等等览妖。

總之轧拄,這是一個魔法語句,它也是協(xié)程的重要組成部分讽膏,至于協(xié)程檩电,還需要繼續(xù)學(xué)習(xí)。

原文:
yield from主要有什么用桅打?



協(xié)程最簡單易懂小例子(補)

Python中的協(xié)程和生成器很相似但又稍有不同是嗜。主要區(qū)別在于:

生成器是數(shù)據(jù)的生產(chǎn)者
協(xié)程則是數(shù)據(jù)的消費者
Python實現(xiàn)的grep就是個很好的例子:

    def grep(pattern):
        print("Searching for", pattern)
        while True:
            line = (yield)       # 接受消息,消息中有pattern就print(line)
            if pattern in line:
                print(line)

等等挺尾!yield返回了什么鹅搪?啊哈,我們已經(jīng)把它變成了一個協(xié)程遭铺。它將不再包含任何初始值丽柿,相反要從外部傳值給它。我們可以通過
send() 方法向它傳值魂挂。這有個例子:

    search = grep('coroutine')
    next(search)
    #output: Searching for coroutine
    search.send("I love you")
    search.send("Don't you love me?")
    search.send("I love coroutine instead!")
    #output: I love coroutine instead!

發(fā)送的值會被yield接收甫题。我們?yōu)槭裁匆\行next()方法呢?這樣做正是為了啟動一個協(xié)程涂召。就像協(xié)程中包含的生成器并不是立刻執(zhí)行坠非,而是通過next()方法來響應(yīng)send()方法。因此果正,你必須通過next()方法來執(zhí)行yield表達式炎码。

我們可以通過調(diào)用close()方法來關(guān)閉一個協(xié)程。像這樣:

    search = grep('coroutine')
    search.close()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秋泳,一起剝皮案震驚了整個濱河市潦闲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迫皱,老刑警劉巖歉闰,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡和敬,警方通過查閱死者的電腦和手機凹炸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來概龄,“玉大人还惠,你說我怎么就攤上這事∷蕉牛” “怎么了蚕键?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長衰粹。 經(jīng)常有香客問我锣光,道長,這世上最難降的妖魔是什么铝耻? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任誊爹,我火速辦了婚禮,結(jié)果婚禮上瓢捉,老公的妹妹穿的比我還像新娘频丘。我一直安慰自己,他們只是感情好泡态,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布搂漠。 她就那樣靜靜地躺著,像睡著了一般某弦。 火紅的嫁衣襯著肌膚如雪桐汤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天靶壮,我揣著相機與錄音怔毛,去河邊找鬼。 笑死腾降,一個胖子當(dāng)著我的面吹牛拣度,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播螃壤,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼抗果,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了映穗?” 一聲冷哼從身側(cè)響起窖张,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤幕随,失蹤者是張志新(化名)和其女友劉穎蚁滋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡辕录,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年睦霎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片走诞。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡副女,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚣旱,到底是詐尸還是另有隱情碑幅,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布塞绿,位于F島的核電站沟涨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏异吻。R本人自食惡果不足惜裹赴,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诀浪。 院中可真熱鬧棋返,春花似錦、人聲如沸雷猪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽春宣。三九已至酵颁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間月帝,已是汗流浹背躏惋。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嚷辅,地道東北人簿姨。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像簸搞,于是被迫代替她去往敵國和親扁位。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

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