8.10. 可等待類別¶
8.10.1. 什麼是可等待物件¶
當協程寫下 await x 時,語言會詢問 x 該如何等待它。任何知道如何回答的物件都是可等待的(awaitable)。共有兩種:
由
async def函式回傳的協程物件。每次呼叫send_request()都會產生一個這樣的物件 -- 是協程物件,而非函式的結果。寫下await send_request()才是真正執行其主體的動作。定義了
__await__方法的類別所產生的物件。async def是第一種的簡寫;__await__則是手動製作第二種的方式。
應用程式碼預設使用第一種。第二種存在是為了少見的情況:當物件本身需要成為呼叫端 await 的對象,而非呼叫該物件某個方法所得到的結果時。
8.10.2. 兩種形式並列對照¶
同樣的邏輯分別以協程與可等待類別寫出:
import asyncio
# async def -- almost always the right choice
async def yield_then_return(value):
await asyncio.sleep_ms(0)
return value
# awaitable class -- equivalent, rarely written
class YieldThenReturn:
def __init__(self, value):
self._value = value
def __await__(self):
yield # let the loop run once
return self._value # value of the ``await`` expression
兩者都可以用相同的方式被 await:
async def main():
a = await yield_then_return(1)
b = await YieldThenReturn(2)
print(a, b) # prints: 1 2
async def 形式較短、讀起來像一般的 Python,並讓語言管理所有暫停與恢復的記帳工作。在這個範例中,類別形式並未得到任何額外好處。
8.10.3. 類別形式何時值得使用¶
當物件必須成為應用程式傳遞的某個東西 -- 而非呼叫端為了取得協程而呼叫的函式 -- 時,類別形式是唯一的選擇:
一個類 future(future-like)的值,由應用程式建立、交給生產者,稍後再
await以取得所產生的結果。建立在現有原語之上的自訂原語,其自然的 API 是
await my_thing而非await my_thing.wait()。asyncio.Task類別本身就是一個內建範例 -- 之所以可以寫await task,是因為Task定義了__await__。
對於任何符合呼叫一個函式、取得一個協程、await 它這種形式的情況,async def 都勝出。
8.10.4. 協定細節¶
__await__ 是一個普通方法(不是 async def),它會回傳一個迭代器。將它寫成產生器 -- 在主體中至少包含一個 yield -- 就會自動使它成為迭代器。每個 yield 都會暫停 await 該物件的協程,並將控制權交還給事件迴圈;產生器會在迴圈下次排程該任務時恢復。單純的 return(或執行到結尾)會結束等待;return 所產生的值會成為 await 運算式的值。
實務上,少數會這麼做的類別會把 yield 的工作交給現有的可等待物件,而非自行驅動迴圈:
class WaitForEvent:
def __init__(self, event):
self._event = event
def __await__(self):
yield from self._event.wait().__await__()
return self._event
yield from 會重複使用底層事件的 __await__ 來進行實際的暫停。大多數 asyncio 應用程式永遠不需要寫出這兩種形式中的任何一種。