python yield from用法

姓名:曾國強

學(xué)號:19021210984

轉(zhuǎn)載自https://blog.csdn.net/qq_27825451/article/details/85244237

【嵌牛導(dǎo)讀】

【嵌牛正文】

一、yield from 的簡單實現(xiàn)

我們了解到,yield是每次“惰性返回”一個值,其實從名字中就能看出,yield from 是yield的升級改進版本,如果將yield理解成“返回”,那么yield from就是“從什么(生成器)里面返回”,這就構(gòu)成了yield from的一般語法,即

yield from generator

這樣的形式。我們通過一個簡單例子來看:

def generator2():

????yield 'a'? ?

????yield 'b'? ?

????yield 'c'? ?

????yield from [11,22,33,44]? ?

????yield from (12,23,34)? ?

????yield from range(3)?

for i in generator2():? ?

????print(i,end=' , ')

'''運行的結(jié)果為:

a , b , c? , 11 , 22 , 33 , 44 , 12 , 23 , 34 , 0 , 1 , 2 ,

'''

總結(jié):

從上面的代碼可以看書,yield from 后面可以跟的可以是“ 生成器 、元組、 列表、range()函數(shù)產(chǎn)生的序列等可迭代對象”

簡單地說,yield from? generator 。實際上就是返回另外一個生成器。而yield只是返回一個元素。從這個層面來說,有下面的等價關(guān)系:yield from iterable本質(zhì)上等于 for item in iterable: yield item 。

二、yield from的高級應(yīng)用

1、針對yield無法獲取生成器return的返回值

我們都知道,在使用yield生成器的時候,如果使用for語句去迭代生成器,則不會顯式的出發(fā)StopIteration異常,而是自動捕獲StopIteration異常,所以如果遇到return,只是會終止迭代,而不會觸發(fā)異常,故而也就沒辦法獲取return的值。如下:

def my_generator():

????for i in range(5):? ? ? ?

????????if i==2:? ? ? ? ? ?

????????????return '我被迫中斷了'? ? ? ?

????????else:? ? ? ? ? ?

????????????yield i

def main(generator):? ?

????try:? ? ?

????????for i in generator:

????????????print(i)? ?

????except StopIteration as exc:? ? ? ?

????????print(exc.value)

g=my_generator()

main(g)

'''運行結(jié)果為:0 1'''

從上面的例子可以看出,for迭代語句不會顯式觸發(fā)異常,故而無法獲取到return的值,迭代到2的時候遇到return語句,隱式的觸發(fā)了StopIteration異常,就終止迭代了,但是在程序中不會顯示出來。

def my_generator():

????for i in range(5):? ? ? ?

????????if i==2:? ? ? ? ? ?

????????????return '我被迫中斷了'? ? ? ?

????????else:? ? ? ? ? ?

????????????yield i

def wrap_my_generator(generator):

????result=yield from generator? ? ?

????print(result)

def main(generator):? ?

????for j in generator:? ? ? ?

????????print(j)

g=my_generator()

wrap_g=wrap_my_generator(g)

main(wrap_g)?

'''運行結(jié)果為:0 1 我被迫中斷了'''

從上面的比較可以看出,yield from具有以下幾個特點:

(1)上面的my_generator是原始的生成器,main是調(diào)用方,使用yield的時候,只涉及到這兩個函數(shù),即“調(diào)用方”與“生成器(協(xié)程函數(shù))”是直接進行交互的,不涉及其他方法,即“調(diào)用方——>生成器函數(shù)(協(xié)程函數(shù))”;

(2)在使用yield from的時候,多了一個對原始my_generator的包裝函數(shù),然后調(diào)用方是通過這個包裝函數(shù)(后面會講到它專有的名詞)來與生成器進行交互的,即“調(diào)用方——>生成器包裝函數(shù)——>生成器函數(shù)(協(xié)程函數(shù))”;

(3)yield from iteration結(jié)構(gòu)會在內(nèi)部自動捕獲 iteration生成器的StopIteration 異常。這種處理方式與 for 循環(huán)處理 StopIteration 異常的方式一樣。而且對 yield from 結(jié)構(gòu)來說,解釋器不僅會捕獲 StopIteration 異常,還會把return返回的值或者是StopIteration的value 屬性的值變成 yield from 表達式的值,即上面的result。

2、yield from所實現(xiàn)的數(shù)據(jù)傳輸通道

前面總結(jié)的幾個特點里面已經(jīng)介紹了yield和yield from的數(shù)據(jù)交互方式,yield涉及到“調(diào)用方與生成器兩者”的交互,生成器通過next()的調(diào)用將值返回給調(diào)用者,而調(diào)用者通過send()方法向生成器發(fā)送數(shù)據(jù);

但是yield還有一個第三者函數(shù),下面將先從相關(guān)的概念說起。

在PEP 380 使用了一些yield from使用的專門術(shù)語:

委派生成器:包含 yield from <iterable> 表達式的生成器函數(shù);即上面的wrap_my_generator生成器函數(shù)

子生成器:從 yield from 表達式中 <iterable> 部分獲取的生成器;即上面的my_generator生成器函數(shù)

調(diào)用方:調(diào)用委派生成器的客戶端代碼;即上面的main生成器函數(shù)

下圖是這三者之間的交互關(guān)系(摘自博客園):


委派生成器在 yield from 表達式處暫停時,調(diào)用方可以直接把數(shù)據(jù)發(fā)給子生成器,子生成器再把產(chǎn)出的值發(fā)給調(diào)用方。子生成器返回之后,解釋器會拋出StopIteration 異常,并把返回值附加到異常對象上,此時委派生成器會恢復(fù)。

總結(jié):

(1)yield from主要設(shè)計用來向子生成器委派操作任務(wù),但yield from可以向任意的可迭代對象委派操作;

(2)委派生成器(group)相當(dāng)于管道,所以可以把任意數(shù)量的委派生成器連接在一起---一個委派生成器使用yield from 調(diào)用一個子生成器,而那個子生成器本身也是委派生成器,使用yield from調(diào)用另一個生成器。

(3)針對yield存在的第二個缺點

首先看一下他要表述的意思是什么?它的局限性在于只能向它的直接調(diào)用者每次yield一個值。這意味著那些包含yield的代碼不能像其他代碼那樣被分離出來放到一個單獨的函數(shù)中。這也正是yield from要解決的。具體參見上文:

https://blog.csdn.net/qq_27825451/article/details/85234610

這句話確實難以理解,但是他要表達的意思實際上是:因為生成器從定義上來看,就像是一個普通的函數(shù),那么既然作為普通函數(shù),就應(yīng)該可以反反復(fù)復(fù)調(diào)用都沒問題的,但是生成器卻并不行。那為什么yield from可以解決這樣的問題呢,主要是因為yield from后面可以跟任意一個生成器,即yield from可以將任意的任務(wù)為派給任意生成器函數(shù),從而避免了子生成器直接向調(diào)用者返回單個值的情況。

三、yield from的用法示例

其實yield from最重要的作用就是提供了一個“數(shù)據(jù)傳輸?shù)墓艿馈?,下面通過一個簡單的例子加以說明為什么是管道:

def average():

????total = 0

????count = 0?

????avg = None

????while True:? ? ? ?

????????num = yield avg? ? ? ?

????????total += num? ? ? ?

????????count += 1? ? ? ?

????????avg = total/count

def wrap_average(generator):? ?

????yield from generator

def main(wrap):? ?

????print(next(wrap))? ?

????print(wrap.send(10))

????print(wrap.send(20))

????print(wrap.send(30))

????print(wrap.send(40))

g = average()

wrap=wrap_average(g)

main(wrap)

'''運行結(jié)果為:None 10.0 15.0 20.0 25.0'''

從上面我們可以發(fā)現(xiàn),調(diào)用方發(fā)送的數(shù)據(jù)是發(fā)給wrap_average的,怎么依然到了生成器函數(shù)average里面呢?這就是“數(shù)據(jù)傳輸管道的作用”。即主函數(shù)調(diào)用方main把各個value傳給grouper ,而這個傳入的值最終到達averager函數(shù)中; grouper并不知道傳入的是什么值,因為從上面的代碼看出,wrap_average里面完全沒有處理這個值的任何代碼!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容