姓名:曾國強
學號:19021210984
轉載自https://blog.csdn.net/qq_27825451/article/details/85244237
【嵌牛導讀】
【嵌牛正文】
一濒翻、yield from 的簡單實現
我們了解到胖缤,yield是每次“惰性返回”一個值颜屠,其實從名字中就能看出,yield from 是yield的升級改進版本堡称,如果將yield理解成“返回”,那么yield from就是“從什么(生成器)里面返回”臭笆,這就構成了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=' , ')
'''運行的結果為:
a , b , c? , 11 , 22 , 33 , 44 , 12 , 23 , 34 , 0 , 1 , 2 ,
'''
總結:
從上面的代碼可以看書盲憎,yield from 后面可以跟的可以是“ 生成器 嗅骄、元組胳挎、 列表饼疙、range()函數產生的序列等可迭代對象”
簡單地說,yield from? generator 慕爬。實際上就是返回另外一個生成器窑眯。而yield只是返回一個元素。從這個層面來說医窿,有下面的等價關系:yield from iterable本質上等于 for item in iterable: yield item 磅甩。
二、yield from的高級應用
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)
'''運行結果為: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)?
'''運行結果為:0 1 我被迫中斷了'''
從上面的比較可以看出,yield from具有以下幾個特點:
(1)上面的my_generator是原始的生成器,main是調用方竞思,使用yield的時候表谊,只涉及到這兩個函數,即“調用方”與“生成器(協程函數)”是直接進行交互的盖喷,不涉及其他方法爆办,即“調用方——>生成器函數(協程函數)”;
(2)在使用yield from的時候课梳,多了一個對原始my_generator的包裝函數距辆,然后調用方是通過這個包裝函數(后面會講到它專有的名詞)來與生成器進行交互的,即“調用方——>生成器包裝函數——>生成器函數(協程函數)”暮刃;
(3)yield from iteration結構會在內部自動捕獲 iteration生成器的StopIteration 異常跨算。這種處理方式與 for 循環(huán)處理 StopIteration 異常的方式一樣。而且對 yield from 結構來說椭懊,解釋器不僅會捕獲 StopIteration 異常诸蚕,還會把return返回的值或者是StopIteration的value 屬性的值變成 yield from 表達式的值,即上面的result氧猬。
2背犯、yield from所實現的數據傳輸通道
前面總結的幾個特點里面已經介紹了yield和yield from的數據交互方式,yield涉及到“調用方與生成器兩者”的交互盅抚,生成器通過next()的調用將值返回給調用者漠魏,而調用者通過send()方法向生成器發(fā)送數據;
但是yield還有一個第三者函數妄均,下面將先從相關的概念說起柱锹。
在PEP 380 使用了一些yield from使用的專門術語:
委派生成器:包含 yield from <iterable> 表達式的生成器函數;即上面的wrap_my_generator生成器函數
子生成器:從 yield from 表達式中 <iterable> 部分獲取的生成器丰包;即上面的my_generator生成器函數
調用方:調用委派生成器的客戶端代碼禁熏;即上面的main生成器函數
下圖是這三者之間的交互關系(摘自博客園):
委派生成器在 yield from 表達式處暫停時,調用方可以直接把數據發(fā)給子生成器烫沙,子生成器再把產出的值發(fā)給調用方匹层。子生成器返回之后,解釋器會拋出StopIteration 異常锌蓄,并把返回值附加到異常對象上升筏,此時委派生成器會恢復。
總結:
(1)yield from主要設計用來向子生成器委派操作任務瘸爽,但yield from可以向任意的可迭代對象委派操作您访;
(2)委派生成器(group)相當于管道,所以可以把任意數量的委派生成器連接在一起---一個委派生成器使用yield from 調用一個子生成器剪决,而那個子生成器本身也是委派生成器灵汪,使用yield from調用另一個生成器檀训。
(3)針對yield存在的第二個缺點
首先看一下他要表述的意思是什么?它的局限性在于只能向它的直接調用者每次yield一個值享言。這意味著那些包含yield的代碼不能像其他代碼那樣被分離出來放到一個單獨的函數中峻凫。這也正是yield from要解決的。具體參見上文:
https://blog.csdn.net/qq_27825451/article/details/85234610
這句話確實難以理解览露,但是他要表達的意思實際上是:因為生成器從定義上來看荧琼,就像是一個普通的函數,那么既然作為普通函數差牛,就應該可以反反復復調用都沒問題的命锄,但是生成器卻并不行。那為什么yield from可以解決這樣的問題呢偏化,主要是因為yield from后面可以跟任意一個生成器脐恩,即yield from可以將任意的任務為派給任意生成器函數,從而避免了子生成器直接向調用者返回單個值的情況侦讨。
三驶冒、yield from的用法示例
其實yield from最重要的作用就是提供了一個“數據傳輸的管道”,下面通過一個簡單的例子加以說明為什么是管道:
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)
'''運行結果為:None 10.0 15.0 20.0 25.0'''
從上面我們可以發(fā)現搭伤,調用方發(fā)送的數據是發(fā)給wrap_average的只怎,怎么依然到了生成器函數average里面呢袜瞬?這就是“數據傳輸管道的作用”怜俐。即主函數調用方main把各個value傳給grouper ,而這個傳入的值最終到達averager函數中邓尤; grouper并不知道傳入的是什么值拍鲤,因為從上面的代碼看出,wrap_average里面完全沒有處理這個值的任何代碼汞扎!