原創不易,轉載請聯繫做者python
深刻理解協程
分爲三部分進行講解:併發
本篇爲深刻理解協程
系列文章的第二篇。框架
yield from
是Python3.3(PEP 380)引入的新語法。主要用於解決在生成器中不方便使用生成器的問題。主要有兩個功能。異步
第一個功能:讓嵌套生成器沒必要再經過循環迭代yield
,而能夠直接使用yield from
。async
看一段代碼:函數
titles = ['Python', 'Java', 'C++'] def func1(titles): yield titles def func2(titles): yield from titles for title in func1(titles): print(title) for title in func2(titles): print(title) # 輸出結果 ['Python', 'Java', 'C++'] Python Java C++
yield
返回的完整的titles
列表,而yield from
返回的是列表中的具體元素。yield from
能夠看做是for title in titles: yield title
的縮寫。這樣就能夠用yield from
減小了一次循環。oop
第二個功能:打開雙向通道,把最外層給調用方與最內層的子生成器連接起來,兩者能夠直接通訊。.net
第二個功能聽起來就讓人頭大。咱們再舉一個例子進行說明:線程
【舉個例子】:經過生成器實現整數相加,經過
send()
函數想生成器中傳入要相加的數字,最後傳入None
結束相加。total
保存結果。code
def generator_1(): # 子生成器 total = 0 while True: x = yield # 解釋4 print(f'+ {x}') if not x: break total += x return total # 解釋5 def generator_2(): # 委託生成器 while True: total = yield from generator_1() # 解釋3 print(f'total: {total}') if __name__ == '__main__': # 調用方 g2 = generator_2() # 解釋1 g2.send(None) # 解釋2 g2.send(2) # 解釋6 g2.send(3) g2.send(None) # 解釋7 # 輸出結果 + 2 + 3 + None total: 5
說明:
解釋1:g2
是調用generator_2()
獲得的生成器對象,做爲協程使用。
解釋2:預激活協程g2
。
解釋3:generator_2
接收的值都會通過yield from
處理,經過管道傳入generator_1
實例。generator_2
會在yield from
處暫停,等待generator_1
實例傳回的值賦值給total
。
解釋4:調用方傳入的值都會傳到這裏。
解釋5:此處返回的total
正是generator_2()
中解釋3處等待返回的值。
解釋6:傳入2進行計算。
解釋7:在計算的結尾傳入None
,跳出generator_1()
的循環,結束計算。
說到這裏,相信看過《深刻理解協程(一):協程的引入》的朋友應該就容易理解上面這段代碼的運行流程了。
藉助上面例子,說明一下隨yield from
一塊兒引入的3個概念:
子生成器
從yield from
獲取任務並完成具體實現的生成器。
委派生成器
包含有 yield from表達式的生成器函數。負責給子生成器委派任務。
調用方
指調用委派生成器的客戶端代碼。
在每次調用send(value)
時,value
不是傳遞給委派生成器,而是藉助yield from
將value
傳遞給了子生成器的yield
。
asyncio
是Python 3.4 試驗性引入的異步I/O框架(PEP 3156),提供了基於協程作異步I/O編寫單線程併發代碼的基礎設施。其核心組件有事件循環(Event Loop)、協程(Coroutine)、任務(Task)、將來對象(Future)以及其餘一些擴充和輔助性質的模塊。
在引入asyncio
的時候,還提供了一個裝飾器@asyncio.coroutine
用於裝飾使用了yield from
的函數,以標記其爲協程。
在實現異步協程以前,咱們先看一個同步的案例:
import time def taskIO_1(): print('開始運行IO任務1...') time.sleep(2) # 假設該任務耗時2s print('IO任務1已完成,耗時2s') def taskIO_2(): print('開始運行IO任務2...') time.sleep(3) # 假設該任務耗時3s print('IO任務2已完成,耗時3s') start = time.time() taskIO_1() taskIO_2() print('全部IO任務總耗時%.5f秒' % float(time.time()-start)) # 輸出結果 開始運行IO任務1... IO任務1已完成,耗時2s 開始運行IO任務2... IO任務2已完成,耗時3s 全部IO任務總耗時5.00094秒
能夠看到,使用同步的方式實現多個IO任務的時間是分別執行這兩個IO任務時間的總和。
下面咱們使用yield from
與asyncio
將上面的同步代碼改爲異步的。修改結果以下:
import time import asyncio @asyncio.coroutine # 解釋1 def taskIO_1(): print('開始運行IO任務1...') yield from asyncio.sleep(2) # 解釋2 print('IO任務1已完成,耗時2s') return taskIO_1.__name__ @asyncio.coroutine def taskIO_2(): print('開始運行IO任務2...') yield from asyncio.sleep(3) # 假設該任務耗時3s print('IO任務2已完成,耗時3s') return taskIO_2.__name__ @asyncio.coroutine def main(): # 調用方 tasks = [taskIO_1(), taskIO_2()] # 把全部任務添加到task中 done,pending = yield from asyncio.wait(tasks) # 子生成器 for r in done: # done和pending都是一個任務,因此返回結果須要逐個調用result() print('協程無序返回值:'+r.result()) if __name__ == '__main__': start = time.time() loop = asyncio.get_event_loop() # 建立一個事件循環對象loop try: loop.run_until_complete(main()) # 完成事件循環,直到最後一個任務結束 finally: loop.close() # 結束事件循環 print('全部IO任務總耗時%.5f秒' % float(time.time()-start)) # 輸出結果 開始運行IO任務2... 開始運行IO任務1... IO任務1已完成,耗時2s IO任務2已完成,耗時3s 協程無序返回值:taskIO_1 協程無序返回值:taskIO_2 全部IO任務總耗時3.00303秒
說明:
解釋1:@asyncio.coroutine
裝飾器是協程函數的標誌,咱們須要在每個任務函數前加這個裝飾器,並在函數中使用yield from
。
解釋2:此處假設該任務運行須要2秒,此處使用異步等待2秒asyncio.sleep(2)
,而非同步等待time.sleep(2)
。
執行過程:
可見,經過使用協程,極大提升了多任務的執行效率,程序最後消耗的時間是任務隊列中耗時最多時間任務的時長。
本篇講述了:
yield from
如何實現協程asyncio
實現異步協程雖然有了yield from
的存在,讓協程實現比以前容易了,可是這種異步協程的實現方式,並非很pythonic
。如今已經不推薦使用了。下篇將與您分享更加完善的Python異步實現方式——async/await實現異步協程
。
Python異步IO之協程(一):從yield from到async的使用
關注公衆號西加加先生
一塊兒玩轉Python。