8.10. 可等待类¶
8.10.1. 什么是可等待对象¶
当协程写下 await x 时,语言会询问 x 如何等待它。任何知道如何回答这个问题的对象都是 可等待的。可等待对象有两种:
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 的值,由应用程序创建,交给生产者,稍后再
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 表达式的值。
在实践中,少数这样做的类会把产出工作交给某个现有的可等待对象,而不是自己驱动循环:
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 应用程序永远都不需要编写这两种形式中的任何一种。