1. 程式人生 > >Python 異步編程:asyncio

Python 異步編程:asyncio

dev log stmt from www prot enter turn except

個人筆記,不保證正確。

雖然說看到很多人不看好 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 能放在三個地方:

  1. async def:用於定義異步函數和異步生成器
    • 不含有 yield 的是 async def 定義的是協程函數(coroutine function),調用該函數返回協程對象(coroutine object)
    • 內部含有 yield 的 async def 定義的是異步生成器函數(asynchronous generator function),調用該函數返回異步生成器(async_generator)
    • async def 中不允許使用 yield from
  2. async for:表示 for 叠代的是一個異步生成器,該 for 循環的每一次叠代,都是異步的。
    • 只能用在 async def 的內部
  3. 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