93人参与 • 2026-05-15 • Python
很多初学者会把 await 理解成“阻塞到结果返回”。这个理解不准确。
在同步代码里,函数调用通常意味着当前线程一路执行到底,中间不能主动把控制权让给别的任务;而在异步代码里,await 的含义更接近:
所以,await 的核心不是“等待”,而是“协作式让出执行权”。
看一个最基本的例子:
import asyncio
async def fetch_data():
print("开始请求")
await asyncio.sleep(1)
print("请求结束")
return {"name": "alice"}
async def main():
result = await fetch_data()
print(result)
asyncio.run(main())
这里真正发生的事情是:
所以,await 是“挂起与恢复”的语言级入口。
不是所有对象都能被 await。python 只接受“awaitable object”,即可等待对象。
可等待对象主要分三类:
先看第一类。
由 async def 定义的函数,调用之后返回的是协程对象。
async def work():
return 42
obj = work()
print(type(obj))
这里的 obj 就是协程对象,它可以被 await。
注意:协程函数和协程对象不是一个东西。
这和普通函数与函数调用结果的区别类似,只不过协程对象不是结果值,而是“尚未执行完成的异步计算描述”。
在 asyncio 体系里,future 表示“未来某个时刻会产生结果”;task 是“被事件循环调度执行的协程”。
import asyncio
async def worker():
await asyncio.sleep(1)
return "done"
async def main():
task = asyncio.create_task(worker())
result = await task
print(result)
asyncio.run(main())
这里 task 不是协程函数,也不是普通值,而是一个 task 对象。它之所以能出现在 await 右边,是因为它本身是 awaitable。
这是 await 协议真正的核心。只要对象实现了 await,并且实现方式符合规范,它就可以被 await。
import asyncio
class delay:
def __init__(self, seconds, value):
self.seconds = seconds
self.value = value
def __await__(self):
return asyncio.sleep(self.seconds, result=self.value).__await__()
async def main():
result = await delay(1, "hello")
print(result)
asyncio.run(main())
这里 delay 不是协程对象,也不是 task,但因为它实现了 await,所以仍然可以被 await。
严格地说,await 协议的核心要求是:
一个对象若想被 await,那么它必须是以下之一:
第三点尤其重要。很多人只记住“实现 await 就行”,这是不完整的。真正的要求是:
await 必须返回一个迭代器,而不是任意对象。
例如下面这个写法是错误的:
class badawaitable:
def __await__(self):
return 123
因为 123 不是迭代器,所以 await badawaitable() 会报错。
正确的方式必须返回一个可迭代推进的对象,最常见做法是直接复用另一个 awaitable 的 await 结果:
import asyncio
class goodawaitable:
def __await__(self):
return asyncio.sleep(1, result="ok").__await__()
这背后的原因是:解释器在执行 await 时,需要一个“可逐步推进”的对象来承载挂起点和恢复点,而这个载体就是迭代器。
这要追溯到 python 异步模型的底层实现思路。
从语义上说,协程的挂起与恢复,和生成器有非常强的亲缘关系。生成器依靠 yield 暂停,下一次 next 或 send 时恢复;协程虽然语法层面写成了 async / await,但底层仍然沿用了“迭代推进状态机”的思想。
因此,await 并不是魔法。可以把它理解成一种受限、专用、语义更清晰的协程委托机制。其底层逻辑与生成器时代的 yield from 有很深的继承关系。
概念上可以把:
result = await obj
近似理解为:
“取出 obj 对应的 await 迭代器,把当前协程的控制流委托给它,直到它结束,再拿到最终结果。”
当然,这只是帮助理解的近似模型,不是源码级等价翻译,但非常接近真实语义。
最稳妥的工程做法通常不是自己手搓底层状态机,而是把已有的 awaitable 组合进去。
例如我们封装一个“延迟后返回结果”的对象:
import asyncio
class sleepthen:
def __init__(self, delay, value):
self.delay = delay
self.value = value
def __await__(self):
async def _inner():
await asyncio.sleep(self.delay)
return self.value
return _inner().__await__()
async def main():
value = await sleepthen(0.5, {"status": "ok"})
print(value)
asyncio.run(main())
这个实现有两个优点:
如果你硬要自己写得更底层,也可以,例如:
import asyncio
class sleepthenlowlevel:
def __init__(self, delay, value):
self.delay = delay
self.value = value
def __await__(self):
yield from asyncio.sleep(self.delay).__await__()
return self.value
async def main():
value = await sleepthenlowlevel(0.5, "done")
print(value)
asyncio.run(main())
这里已经非常接近协议本体了:
这个写法能更直观地展示:await 协议本质上是“以迭代器为载体的挂起协议”。
这一点必须讲清,因为它能把“挂起”和“返回值”统一起来。
当你写:
result = await some_obj
你得到的 result,并不是 some_obj 本身,而是“这个 awaitable 完成后产出的最终值”。
例如:
import asyncio
async def compute():
await asyncio.sleep(0.2)
return 100
async def main():
x = await compute()
print(x)
asyncio.run(main())
输出是 100。
从底层协议视角看,这个“最终值”在迭代器语义里通常体现为终止时携带的值。对生成器模型熟悉的人会知道,生成器结束时可以通过 stopiteration.value 把值带出来。python 的协程模型沿用了这一思路,只是语言层已经把细节包装掉了。
所以,await 做的不是“读取某个对象的字段”,而是“驱动某个异步状态机直到完成,并提取它的完成值”。
await 协议本身只规定“对象如何可等待”,并不直接等于“调度机制”。真正负责调度的是事件循环。
这点很关键。因为很多人会把 await 和 asyncio 混为一谈,实际上两者分工不同:
看一个更接近底层的例子:
import asyncio
async def wait_on_future():
loop = asyncio.get_running_loop()
future = loop.create_future()
def complete():
print("设置 future 结果")
future.set_result("future done")
loop.call_later(1, complete)
print("开始等待")
result = await future
print("拿到结果:", result)
asyncio.run(wait_on_future())
这里的关键流程是:
由此可以看出:
await 协议告诉解释器“这个对象可以挂起我”,而事件循环负责“什么时候恢复你”。
task 很值得单独讲,因为它正好体现了“协议”和“调度”的结合。
import asyncio
async def worker():
await asyncio.sleep(1)
return "worker result"
async def main():
task = asyncio.create_task(worker())
print("task 已创建")
result = await task
print(result)
asyncio.run(main())
这里 worker() 先产生协程对象,create_task 再把它包装成 task。task 由事件循环推进执行,因此:
所以 task 是异步执行的“运行态对象”,协程对象更像“待运行描述”。
工程里经常要判断一个对象能不能 await,这时不能只看它是不是协程对象。
错误写法:
import inspect
def only_coroutine(obj):
return inspect.iscoroutine(obj)
这会漏掉很多合法 awaitable,比如 task、future、自定义实现了 await 的对象。
更合理的方式是:
import inspect
import asyncio
class custom:
def __await__(self):
return asyncio.sleep(0).__await__()
async def demo():
coro = asyncio.sleep(0)
task = asyncio.create_task(asyncio.sleep(0))
custom = custom()
print(inspect.isawaitable(coro)) # true
print(inspect.isawaitable(task)) # true
print(inspect.isawaitable(custom)) # true
await coro
await task
await custom
asyncio.run(demo())
因此,在动态调度、依赖注入、框架执行器里,通常应当优先使用 inspect.isawaitable,而不是只检查 inspect.iscoroutine。
这是语法层面的硬约束。
在现代 python 中,await 只能出现在 async def 定义的函数体内。也就是说,await 本身不是一个可以到处随便写的表达式,它只能存在于“异步上下文”中。
例如下面是非法的:
import asyncio await asyncio.sleep(1)
在普通 python 脚本顶层这会报语法错误。正确写法通常是:
import asyncio
async def main():
await asyncio.sleep(1)
asyncio.run(main())
不过在某些交互环境里,比如部分 notebook 或 repl,会支持顶层 await,这是宿主环境额外提供的能力,不是普通脚本的默认行为。
看这段代码:
def sync_add(a, b):
return a + b
async def async_add(a, b):
return a + b
调用它们:
x = sync_add(1, 2) y = async_add(1, 2) print(x) # 3 print(y) # 协程对象,不是 3
这说明:
必须再经过 await,才能得到最终值:
import asyncio
async def async_add(a, b):
return a + b
async def main():
y = await async_add(1, 2)
print(y) # 3
asyncio.run(main())
所以,await 不是“异步函数调用语法糖”,而是“异步结果兑现机制”。
await 不只是传值,也会传异常。
import asyncio
async def bad():
await asyncio.sleep(0.1)
raise valueerror("boom")
async def main():
try:
await bad()
except valueerror as exc:
print("捕获到异常:", exc)
asyncio.run(main())
这里 bad 内部抛出的异常,会在 await bad() 处重新表现出来。
这说明 await 的语义和普通函数调用很像的一点在于:
只是它们之间多了一层“挂起与恢复”。
在 asyncio 中,任务可以被取消,这通常通过 cancellederror 体现。
import asyncio
async def worker():
try:
print("开始工作")
await asyncio.sleep(10)
except asyncio.cancellederror:
print("收到取消请求")
raise
async def main():
task = asyncio.create_task(worker())
await asyncio.sleep(0.2)
task.cancel()
try:
await task
except asyncio.cancellederror:
print("task 已取消")
asyncio.run(main())
这里的重点不是“取消 api 怎么用”,而是理解其与 await 的关系:
这说明 await 协议并不只承载“最终成功值”,也承载“失败与取消”的控制流。
如果想真正理解 await 协议,必须知道它和 yield from 的历史关系。
在 async / await 语法出现前,python 曾使用“基于生成器的协程”。当时异步协作常通过 yield from 来表达。
现代 python 里,await 可以理解为对异步委托的专门化语法,它比 yield from 更严格、更清晰,原因包括:
所以,await 不是凭空创造的新机制,而是在生成器语义基础上为异步编程建立的专用协议层。
下面这个例子能帮助你从“语言糖”退回到“协议视角”。
import asyncio
class oneshotvalue:
def __init__(self, value):
self.value = value
def __await__(self):
if false:
yield
return self.value
async def main():
result = await oneshotvalue(123)
print(result)
asyncio.run(main())
这个例子看起来有些奇怪,尤其是:
if false:
yield
它的作用是让这个函数在语法上成为生成器,从而返回一个迭代器对象。虽然不会真的 yield 出什么,但协议上它已经满足“返回迭代器”的要求了,因此 await 能工作。
这个例子很好地说明了一件事:
await 关心的不是“这个对象是不是某个具体类”,而是“它能否通过迭代器协议表达挂起/完成语义”。
不过,这种写法更适合教学,不适合生产环境。工程里一般应当复用已有 awaitable,而不是手写这种技巧性实现。
有几个概念很容易混淆,必须分开。
异步迭代使用的是:
对应语法是 async for,而不是 await。
异步上下文管理使用的是:
对应语法是 async with,而不是 await。
一个对象能被 for 遍历,不代表它能被 await。await 要求的是 awaitable 协议,不是 iterable 协议。
例如列表、普通生成器都不能直接 await。
错误理解:
“我调用了异步函数,所以它开始运行了。”
实际上,单纯调用 async def 函数只会得到协程对象,不保证执行。执行通常要通过以下方式之一触发:
不准确。await 会挂起当前协程,但只要底层等待对象是非阻塞式的,事件循环仍然可以在同一线程调度其他任务。
最典型错误就是返回普通值、列表、协程函数本身,而不是迭代器。
可调用对象和可等待对象是两个不同维度。
如果你站在框架作者的角度看,await 协议的价值非常大,因为它提供了一层统一抽象:
不管右边是:
只要符合协议,调用方都可以统一写成:
result = await obj
这意味着上层业务逻辑不需要关心底层异步对象的具体类型,只需要关心它是否满足 awaitable 语义。
这也是为什么“协议”比“类继承”更重要。python 在这里采用的是典型的鸭子类型哲学:
你不必继承某个统一基类,只要行为符合协议即可。
import asyncio
import inspect
class resourceloader:
def __init__(self, name, delay):
self.name = name
self.delay = delay
def __await__(self):
async def _load():
await asyncio.sleep(self.delay)
if self.name == "bad":
raise runtimeerror("resource load failed")
return {"resource": self.name}
return _load().__await__()
async def consume(obj):
if not inspect.isawaitable(obj):
raise typeerror("对象不可等待")
return await obj
async def main():
task = asyncio.create_task(consume(resourceloader("user", 0.5)))
result = await task
print(result)
try:
await consume(resourceloader("bad", 0.1))
except runtimeerror as exc:
print("异常传播成功:", exc)
asyncio.run(main())
这段代码展示了四件事:
可以把整个 await 协议总结为下面四条:
如果再进一步压缩成一句更偏底层的话:
await 协议就是“用迭代器描述异步挂起点,并由事件循环负责恢复执行”的一套语言约定。
建议你把 python 异步系统拆成三层看:
语法层
async def、await、async for、async with
协议层
await、aiter、anext、aenter、aexit
调度层
asyncio event loop、future、task、回调恢复
其中,await 协议位于“语法层”和“调度层”之间。它既不是纯语法糖,也不是完整调度器;它是把“可挂起对象”接入异步执行体系的桥。
这一层真正理解了,很多问题都会自然变得清楚:
到此这篇关于python之await 协议的实现的文章就介绍到这了,更多相关python await 协议内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论