Python 異步編程:asyncio
個人筆記,不保證正確。
雖然說看到很多人不看好 asyncio,但是這個東西還是必須學的。。
基於協程的異步,在很多語言中都有,學會了 Python 的,就一通百通。
一、生成器 generator
Python 的 asyncio 是通過 generator 實現的,要學習 async,先得復習下 generator.
1. yield
眾所周知,yield 是用於定義 generator 函數的關鍵字,調用該函數,會返回一個 generator
>>> def f(): ... yield 1 ... yield 2 ... >>> f() # 返回的是 generator <generator object f at 0x7f672c460570> >>> g = f() >>> next(g) # 通過 next 方法從 generator 獲取值 1 >>> g.__next__() # next 方法實際是調用了 generator 的 __next__ 方法 2 >>> next(g) # 生成器運行結束,產生一個 StopIteration 的 exception Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
每次調用 next,generator 都只會運行到下一個 yield 關鍵字所在行,返回 yield 右側的對象,然後暫停在該處,等待下一次 next 調用。
從上面的例子看,yield 就是延遲求值而已。但是 yield 還有一個特性,就是它是一個 expression,有返回值!看例子:
>>> def func(): ... r = yield 1 ... yield r ... >>> g = func() >>> next(g) 1 >>> next(g) # 通過 next 調用,yield 的返回值為 None >>> g2 = func() >>> next(g2) # 首先需要通過 next 調用,運行到 yield 語句處 1 >>> g2.send(419) # 現在用 send 方法,這會將當前所在的 yield 語句的值設置為你 send 的值,也就是 419 419 # 然後 generator 運行到下一個 yield,返回右邊的值並暫停
generator 有四個實例函數:next、send 是剛剛已經介紹了的,此外還有 throw 用於從 yield 所在處拋出 Exception,和 close 用於關閉 Generator。詳見 Generator-iterator methods
2. yield from
可以理解成是 yield <value> from <iterable>
,每次調用時它都會從 <iterable> 中取值,直到遇到 StopIteration。才會從下一個 yield 取值。
>>> def f(): ... yield from [1, 2, 3, 4] # iterable ... yield 5 ... yield from range(4, 0, -1) # iterable ... >>> list(f()) [1, 2, 3, 4, 5, 4, 3, 2, 1]
當然,yield from <iterable>
也是一個 expression,也有值。它的值就是 StopIteration 異常的第一個參數,內置類型的這個值都是 None.
>>> def f():
... r = yield from [1, 2]
... yield f"value of yield from is {r}"
...
>>> list(f())
[1, 2, 'value of yield from is None']
當 <iterable> 是 generator 時,yield from
會直接將函數調用委托給這個子 generator,這裏的調用包括了前面說過的 next、send、throw、close 四個函數。
並直接將 sub generator yield 的值 yield 給 caller.
3. yield 和 return 混用會發生什麽?
generator 中的 return value
,語義上等同於 rasie StopIteration(value)
:
>>> def f():
... yield 1
... return 2
... yield 3 # 永遠不會被執行
...
>>> g = f()
>>> next(g)
1
>>> next(g) # return 引發 StopIteration
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration: 2
>>> next(g) # 再次調用,StopIteration 變成無參了。
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
可以看到 return 引發了 StopIteration 異常,而 return 的值則成了該異常的第一個參數。
之前說過 yield from <sub generator>
表達式的值,就是該 <sub generator> 的 StopIteration 異常的第一個參數,因此:
>>> def f2():
... a = yield from f()
... yield a # a 是 f() 中 return 的值
...
>>> list(f2())
[1, 2]
PEP 479 -- Change StopIteration handling inside generators 修改了StopIteration 的行為,該 PEP 使人為 raise 的 StopIteration 引發一個 RuntimeError。
該 PEP 在 Python 3.5 版本添加到 future 中,並在 Python 3.7 成為默認行為。
因此除非你確實想要引發異常,否則應該使用 return 來結束一個 generator 並返回值。
二、異步IO、協程與非阻塞 IO
先了解一下 進程線程協程與並發並行 和 各種 IO 模型
三、asyncio 的簡單使用
asyncio 引入了兩個新關鍵字:async 和 await,其中 async 能放在三個地方:
- async def:用於定義異步函數和異步生成器
- 不含有 yield 的是 async def 定義的是協程函數(coroutine function),調用該函數返回協程對象(coroutine object)
- 內部含有 yield 的 async def 定義的是異步生成器函數(asynchronous generator function),調用該函數返回異步生成器(async_generator)
- async def 中不允許使用 yield from
- async for:表示 for 叠代的是一個異步生成器,該 for 循環的每一次叠代,都是異步的。
- 只能用在 async def 的內部
- async with:表示 with 管理的是一個異步上下文管理器(asynchronous context manager)
- 該 context manager 的 enter 和 exit 兩個步驟是異步的
- 只能用在 async def 的內部
註意異步 generator、context manager,它的 protocol 都和同步的不同,不能混為一談。
具體而言,對同步 protocol xxx 函數,它的異步版本為 axxx,就是加個 a。
而 await,就相當於 yield from,差別在於 await 是異步的。
在 yield from 中,當前生成器調用另一個生成器,當前生成器會一直等待直到另一個生成器返回。
但是在 await 中,當前生成器等待返回過程中, eventloop 會尋找其他 task 來跑,這就利用上了 IO 漫長的等待時間。
async for 是每次叠代都會 await 一次,如果叠代對象是 IO 操作,這個 IO 等待時間就會被利用上。
async with 也是同樣,如果 context 的 enter 和 exit 是 IO 操作,這個 IO 時間就會被 eventloop 用於運行其他 task.
使用 asyncio 時,我們要讓所有的 IO 操作都成為異步操作。一個 IO 操作配一個 await.
asyncio 內部原理
待續
參考
- 從0到1,Python異步編程的演進之路
- 怎麽掌握 asyncio
- Python Async/Await入門指南
- 談談Python協程技術的演進
- Python Doc - Coroutines
Python 異步編程:asyncio