Python異步編程之yield?from的用法詳解
yield from 簡介
yield from 是Python3.3 后新加的語言結(jié)構(gòu),可用于簡化yield表達(dá)式的使用。
yield from 簡單示例:
>>> def gen(): ... yield from range(10) ... >>> g = gen() >>> next(g) 0 >>> next(g) 1 >>>
yield from 用于獲取生成器中的值,是對yield使用的一種優(yōu)化。
yield from 兩個最重要的特點:
1.在調(diào)用包含yield from
的函數(shù)時,程序會停在yield from
這里,并將for循環(huán)的執(zhí)行傳遞到子生成器里面去。相當(dāng)于直接調(diào)用子生成器。這個功能可以稱之為傳輸通道
2.子生成器中的return,會被 res = yield from
捕獲,并賦值給res。這個可以稱之為異常處理
傳輸通道
生成器存在這樣一種調(diào)用場景,有生成器A,生成器B調(diào)用A迭代取值。main函數(shù)從迭代生成器B獲取元素。這就是所謂的嵌套生成器。如果要迭代出最內(nèi)層生成器的值,可以用如下方法:
>>> def sub_gen(): ... for i in range(5): ... yield i ... return 100 ... >>> def gen(): ... g = sub_gen() ... while True: ... try: ... temp = next(g) ... yield temp ... except StopIteration as e: ... print(f"sub_gen return {e.value}") ... return ... >>> g = gen() >>> for i in g: ... print(i) ... 0 1 2 3 4 sub_gen return 100
首先在外層生成器中使用while True 循環(huán),通過next迭代內(nèi)層生成器的值,然后捕獲異常獲取內(nèi)層生成器的返回。
使用 yield from 不需要這兩個動作就能完成同樣的功能,有效減少代碼復(fù)雜度。
>>> def sub_gen(): ... for i in range(5): ... yield i ... return 100 ... >>> def gen(): ... g = sub_gen() ... res = yield from g ... print(f"sub_gen return:{res}") ... >>> g = gen() >>> for i in g: ... print(i) ... 0 1 2 3 4 sub_gen return:100
執(zhí)行流程:
當(dāng)程序執(zhí)行到 yield from 時,會暫停在這里,將for循環(huán)作用到內(nèi)層迭代器,也就是g = sub_gen()
中的g變量。直到sub_gen執(zhí)行到return拋出異常被yield from捕獲,這個調(diào)用算是結(jié)束。這就是yield from的傳輸通道
注意:除了可以通過yield from 傳輸通道的能力迭代取值,也可以通過send發(fā)送值到子生成器中
異常處理
在上一篇yield使用中說明過迭代生成器時遇到return會拋出異常,獲取返回值需要捕獲異常再取值,而yield from 的功能之二就是捕獲了異常,獲取到return的值,賦值給變量。
def sub_gen(): for i in range(5): yield i return 100 def gen(): g = sub_gen() res = yield from g print(f"捕獲返回值:{res}") def main(): g = gen() for i in g: print(i) main()
執(zhí)行過程:
使用for循環(huán)迭代g,相當(dāng)于for循環(huán)迭代sub_gen()。
sub_gen 生成器最后的返回值作為異常拋出,調(diào)用方需要捕獲異常才能獲取返回值。但是有了yield from
之后,sub_gen生成器的返回值異常就會被yield from捕獲,賦值給res變量。這就是yield from能夠處理內(nèi)層生成器的返回值。這就是yield from的異常捕獲能力
yield from 專用術(shù)語
yield from使用的專門術(shù)語:
委派生成器:包含 yield from 表達(dá)式的生成器函數(shù);即上面的gen生成器函數(shù)
子生成器:yield from 從中取值的生成器;即上面的sub_gen生成器函數(shù)
調(diào)用方:調(diào)用委派生成器的客戶端代碼;即上面的main生成器函數(shù)
三者之間的關(guān)系如下:
委派生成器在 yield from 表達(dá)式處暫停時,調(diào)用方可以直接迭代子生成器,子生成器把產(chǎn)出的值發(fā)給調(diào)用方。子生成器返回之后,解釋器會拋出StopIteration 異常,yield from會捕獲異常并取值,然后委派生成器會恢復(fù)。
yield from 實現(xiàn)的協(xié)程
在Python中有多種方式可以實現(xiàn)協(xié)程,例如:
- greenlet 是一個第三方模塊,用于實現(xiàn)協(xié)程代碼(Gevent協(xié)程就是基于greenlet實現(xiàn))
- yield 生成器,借助生成器的特點也可以實現(xiàn)協(xié)程代碼。
- asyncio 在Python3.4中引入的模塊用于編寫協(xié)程代碼。
- async & awiat 在Python3.5中引入的兩個關(guān)鍵字,結(jié)合asyncio模塊可以更方便的編寫協(xié)程代碼。
在Python3.4之前官方未提供協(xié)程的類庫,一般大家都是使用greenlet等其他來實現(xiàn)。在Python3.4發(fā)布后官方正式支持協(xié)程,即:asyncio模塊。
在Python3.4-Python3.11的代碼中可以通過asyncio + yield from的方法來實現(xiàn)原生協(xié)程。
import time import asyncio @asyncio.coroutine def task1(): print('開始運(yùn)行IO任務(wù)1...') yield from asyncio.sleep(2) # 假設(shè)該任務(wù)耗時2s print('IO任務(wù)1已完成,耗時2s') return task1.__name__ @asyncio.coroutine def task2(): print('開始運(yùn)行IO任務(wù)2...') yield from asyncio.sleep(3) # 假設(shè)該任務(wù)耗時3s print('IO任務(wù)2已完成,耗時3s') return task2.__name__ @asyncio.coroutine def main(): # 把所有任務(wù)添加到task中 tasks = [task1(), task2()] # 子生成器 done, pending = yield from asyncio.wait(tasks) # done和pending都是一個任務(wù),所以返回結(jié)果需要逐個調(diào)用result() for r in done: print(f'協(xié)程返回值:r.result()') if __name__ == '__main__': start = time.time() # 創(chuàng)建一個事件循環(huán)對象loop loop = asyncio.get_event_loop() try: # 完成事件循環(huán),直到最后一個任務(wù)結(jié)束 loop.run_until_complete(main()) finally: loop.close() print('所有IO任務(wù)總耗時%.5f秒' % float(time.time()-start))
代碼解釋:
- @asyncio.coroutine 裝飾的生成器函數(shù)代表著一個任務(wù)
- yield from asyncio.sleep(3) 模擬一個IO操作,協(xié)程遇到IO會自動切換
- loop.run_until_complete(main()) 啟動一個事件循環(huán),在循環(huán)中執(zhí)行所有任務(wù)。任務(wù)遇到IO自動切換
輸出:
/Users/yield_from_demo.py:14: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
def task2():
/Users/yield_from_demo.py:22: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead
def main():
/Users/yield_from_demo.py:28: DeprecationWarning: The explicit passing of coroutine objects to asyncio.wait() is deprecated since Python 3.8, and scheduled for removal in Python 3.11.
done, pending = yield from asyncio.wait(tasks)
開始運(yùn)行IO任務(wù)1...
開始運(yùn)行IO任務(wù)2...
IO任務(wù)1已完成,耗時2s
IO任務(wù)2已完成,耗時3s
協(xié)程返回值:r.result()
協(xié)程返回值:r.result()
所有IO任務(wù)總耗時3.00188秒
以上就是Python異步編程之yield from的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于Python yield from的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
pandas數(shù)據(jù)處理之繪圖的實現(xiàn)
這篇文章主要介紹了pandas數(shù)據(jù)處理之繪圖的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06pyqt5實現(xiàn)繪制ui,列表窗口,滾動窗口顯示圖片的方法
今天小編就為大家分享一篇pyqt5實現(xiàn)繪制ui,列表窗口,滾動窗口顯示圖片的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-06-06python argparse傳入布爾參數(shù)false不生效的解決
這篇文章主要介紹了python argparse傳入布爾參數(shù)false不生效的解決,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04