Python 3中的yield from語(yǔ)法詳解
前言
最近在搗鼓Autobahn,它有給出個(gè)例子是基于asyncio 的,想著說(shuō)放到pypy3上跑跑看竟然就……失敗了。 pip install asyncio直接報(bào)invalid syntax,粗看還以為2to3處理的時(shí) 候有問(wèn)題——這不能怪我,好~多package都是用2寫了然后轉(zhuǎn)成3的——結(jié)果發(fā) 現(xiàn)asyncio本來(lái)就只支持3.3+的版本,才又回頭看代碼,赫然發(fā)現(xiàn)一句 yield from;yield我知道,但是yield from是神馬?
PEP-380
好吧這個(gè)標(biāo)題是我google出來(lái)的,yield from的前世今生都在 這個(gè)PEP里面,總之大意是原本的yield語(yǔ)句只能將CPU控制權(quán) 還給直接調(diào)用者,當(dāng)你想要將一個(gè)generator或者coroutine里帶有 yield語(yǔ)句的邏輯重構(gòu)到另一個(gè)generator(原文是subgenerator) 里的時(shí)候,會(huì)非常麻煩,因?yàn)橥饷娴膅enerator要負(fù)責(zé)為里面的 generator做消息傳遞;所以某人有個(gè)想法是讓python把消息傳遞 封裝起來(lái),使其對(duì)程序猿透明,于是就有了yield from。
PEP-380規(guī)定了yield from的語(yǔ)義,或者說(shuō)嵌套的generator應(yīng)該 有的行為模式。
假設(shè)A函數(shù)中有這樣一個(gè)語(yǔ)句
yield from B()
B()返回的是一個(gè)可迭代(iterable)的對(duì)象b,那么A()會(huì)返回一個(gè) generator——照我們的命名規(guī)范,名字叫a——那么:
- b迭代產(chǎn)生的每個(gè)值都直接傳遞給a的調(diào)用者。
- 所有通過(guò)send方法發(fā)送到a的值都被直接傳遞給b. 如果發(fā)送的 值是None,則調(diào)用b的
__next__()方法,否則調(diào)用b的send 方法。如果對(duì)b的方法調(diào)用產(chǎn)生StopIteration異常,a會(huì)繼續(xù) 執(zhí)行yield from后面的語(yǔ)句,而其他異常則會(huì)傳播到a中,導(dǎo) 致a在執(zhí)行yield from的時(shí)候拋出異常。 - 如果有除GeneratorExit以外的異常被throw到a中的話,該異常 會(huì)被直接throw到b中。如果b的throw方法拋出StopIteration, a會(huì)繼續(xù)執(zhí)行;其他異常則會(huì)導(dǎo)致a也拋出異常。
- 如果一個(gè)GeneratorExit異常被throw到a中,或者a的close 方法被調(diào)用了,并且b也有close方法的話,b的close方法也 會(huì)被調(diào)用。如果b的這個(gè)方法拋出了異常,則會(huì)導(dǎo)致a也拋出異常。 反之,如果b成功close掉了,a也會(huì)拋出異常,但是是特定的 GeneratorExit異常。
- a中
yield from表達(dá)式的求值結(jié)果是b迭代結(jié)束時(shí)拋出的 StopIteration異常的第一個(gè)參數(shù)。 - b中的
return <expr>語(yǔ)句實(shí)際上會(huì)拋出StopIteration(<expr>)異常,所以b中return的值會(huì)成為a中yield from表達(dá)式的返回值。
為神馬會(huì)有這么多要求?因?yàn)間enerator這種東西的行為在加入throw 方法之后變得非常復(fù)雜,特別是幾個(gè)generator在一起的情況,需要 類似進(jìn)程管理的元語(yǔ)對(duì)其進(jìn)行操作。上面的所有要求都是為了統(tǒng)一 generator原本就復(fù)雜的行為,自然簡(jiǎn)單不下來(lái)啦。
我承認(rèn)我一下沒(méi)看明白PEP的作者到底想說(shuō)什么,于是動(dòng)手“重構(gòu)” 一遍大概會(huì)有點(diǎn)幫助。
一個(gè)沒(méi)用的例子
說(shuō)沒(méi)用是因?yàn)槟愦蟾挪粫?huì)真的想把程序?qū)懗蛇@樣,但是……反正能說(shuō)明 問(wèn)題就夠了。
設(shè)想有這樣一個(gè)generator函數(shù):
def inner(): coef = 1 total = 0 while True: try: input_val = yield total total = total + coef * input_val except SwitchSign: coef = -(coef) except BreakOut: return total
這個(gè)函數(shù)生成的generator將從send方法接收到的值累加到局部 變量total中,并且在收到BreakOut異常時(shí)停止迭代;至于另外 一個(gè)SwitchSign異常應(yīng)該不難理解,這里就不劇透了。
從代碼上看,由inner()函數(shù)得到的generator通過(guò)send接收用于 運(yùn)算的數(shù)據(jù),同時(shí)通過(guò)throw方法接受外部代碼的控制以執(zhí)行不同 的代碼分支,目前為止都很清晰。
接下來(lái)因?yàn)樾枨笥凶儎?dòng),我們需要在inner()這段代碼的前后分別加 入初始化和清理現(xiàn)場(chǎng)的代碼。鑒于我認(rèn)為“沒(méi)壞的代碼就不要?jiǎng)印?,?決定讓inner()維持現(xiàn)狀,然后再寫一個(gè)outer() ,把添加的代碼放在 outer()里,并提供與inner()一樣的操作接口。由于inner()利用了 generator的若干特性,所以outer()也必須做到這五件事情:
outer()必須生成一個(gè)generator;- 在每一步的迭代中,
outer()要幫助inner()返回迭代值; - 在每一步的迭代中,
outer()要幫助inner()接收外部發(fā)送的數(shù)據(jù); - 在每一步的迭代中,
outer()要處理inner()接收和拋出所有異常; - 在
outer()被close的時(shí)候,inner()也要被正確地close掉。
根據(jù)上面的要求,在只有yield的世界里,outer()可能是長(zhǎng)這樣的:
def outer1():
print("Before inner(), I do this.")
i_gen = inner()
input_val = None
ret_val = i_gen.send(input_val)
while True:
try:
input_val = yield ret_val
ret_val = i_gen.send(input_val)
except StopIteration:
break
except Exception as err:
try:
ret_val = i_gen.throw(err)
except StopIteration:
break
print("After inner(), I do that.")
WTF,這段代碼比inner()本身還要長(zhǎng),而且還沒(méi)處理close操作。
現(xiàn)在我們來(lái)試試外星科技:
def outer2():
print("Before inner(), I do this.")
yield from inner()
print("After inner(), I do that.")
除了完全符合上面的要求外,這四行代碼打印出來(lái)的時(shí)候還能省點(diǎn)紙。
我們可以在outer1()和outer2()上分別測(cè)試 數(shù)據(jù) 以及 異常 的傳遞,不難發(fā)現(xiàn)這兩個(gè)generator的行為基本上是一致的。既然如此, 外星科技當(dāng)然在大多數(shù)情況下是首選。
對(duì)generator和coroutine的疑問(wèn)
從以前接觸到Python下的coroutine就覺(jué)得它怪怪的,我能看清它們的 行為模式,但是并不明白為什么要使用這種模式,generator和 coroutine具有一樣的對(duì)外接口,是generator造就了coroutine呢,還 是coroutine造就了generator?最讓我百思不得其解的是,Python下 的coroutine將“消息傳遞”和“調(diào)度”這兩種操作綁在一個(gè)yield 上——即便有了yield from,這個(gè)狀況還是沒(méi)變過(guò)——我看不出這樣做 的必要性。如果一開始就從語(yǔ)法層面將這兩種語(yǔ)義分開,并且為 generator和coroutine分別設(shè)計(jì)一套接口,coroutine的概念大概也會(huì) 容易理解一些。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家學(xué)習(xí)或者使用python能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
相關(guān)文章
Django框架ORM數(shù)據(jù)庫(kù)操作實(shí)例詳解
這篇文章主要介紹了Django框架ORM數(shù)據(jù)庫(kù)操作,結(jié)合實(shí)例形式詳細(xì)分析了Django框架ORM數(shù)據(jù)庫(kù)基本增刪改查與相關(guān)函數(shù)使用技巧,需要的朋友可以參考下2019-11-11
Django實(shí)現(xiàn)靜態(tài)文件緩存到云服務(wù)的操作方法
這篇文章主要介紹了Django實(shí)現(xiàn)靜態(tài)文件緩存到云服務(wù)的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
python requests 庫(kù)請(qǐng)求帶有文件參數(shù)的接口實(shí)例
今天小編就為大家分享一篇python requests 庫(kù)請(qǐng)求帶有文件參數(shù)的接口實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
如何利用python實(shí)現(xiàn)windows的批處理及文件夾操作
最近工作中需要幾個(gè)腳本運(yùn)行其他程序,幾乎像一個(gè)Windows批處理文件,這篇文章主要給大家介紹了關(guān)于如何利用python實(shí)現(xiàn)windows的批處理及文件夾操作的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-01-01
python 循環(huán)結(jié)構(gòu)練習(xí)題
這篇文章主要給大家分享的是python 循環(huán)結(jié)構(gòu)練習(xí)題,求兩個(gè)數(shù)最大公約數(shù)、整數(shù)反轉(zhuǎn):如12345,輸出54321等多個(gè)練習(xí)題,需要的朋友可以參考一下2021-11-11
使用Python快速實(shí)現(xiàn)鏈接轉(zhuǎn)word文檔
這篇文章主要為大家詳細(xì)介紹了如何使用Python快速實(shí)現(xiàn)鏈接轉(zhuǎn)word文檔功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02
學(xué)習(xí)createTrackbar的使用方法及步驟
這篇文章主要為大家介紹了學(xué)習(xí)createTrackbar的使用方法及步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10
python中selenium操作下拉滾動(dòng)條的幾種方法匯總
這篇文章主要介紹了python中selenium操作下拉滾動(dòng)條的幾種方法匯總,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07

