await只能用于协程对象、Task或Future,常见错误包括误await普通函数(如time.sleep)、未调用async函数(写成await func而非await func())、混用同步库(如requests);必须在async函数内使用,且需确保表达式返回可等待对象。

await 后面报 RuntimeError: await wasn't used with a valid awaitable?
这是最常见的错误,本质是 await 操作符被用在了非可等待对象(non-awaitable)上。Python 不会自动把普通函数、列表、字符串、None 或同步返回值变成协程——它只认三类东西:coroutine 对象(由 async def 定义)、Task、Future。
常见踩坑点:
- 误把普通函数(没加
async)直接await,比如await time.sleep(1)→ 应该用await asyncio.sleep(1) - 调用了
async def函数但忘了加括号,写成await fetch_data(只是函数对象)→ 正确是await fetch_data()(得到 coroutine 对象) - 混用同步库的阻塞调用,如
await requests.get(...)→requests是同步的,不返回可等待对象;得换aiohttp或用loop.run_in_executor
async def 函数里哪些东西能直接 await?
只有明确返回协程、Task 或 Future 的表达式才能跟在 await 后面。不是“带 async 就能 await”,而是“调用后生成协程对象”才行。
典型可 await 场景:
立即学习“Python免费学习笔记(深入)”;
-
await另一个async def函数的调用结果(如await db.query()) -
awaitasyncio.create_task(...)返回的Task -
awaitasyncio.ensure_future(...)或loop.create_future()返回的Future -
await支持__await__协议的对象(如asyncio.Lock、asyncio.Queue.get())
注意:asyncio.sleep() 能 await,是因为它本身是 async def 函数;但 time.sleep() 是普通函数,返回 None,不能 await。
为什么不能在普通函数或模块顶层用 await?
await 只能在 async def 函数内部使用,这是语法硬性限制。Python 解释器在编译阶段就检查上下文:如果当前作用域不是协程函数,直接报 SyntaxError: invalid syntax。
常见误解与替代方案:
- 想在脚本开头就 await 一个初始化操作?→ 包进
async def main(): ...,再用asyncio.run(main()) - 想在类方法里 await 但没加
async?→ 方法必须声明为async def method(self):,否则await语法非法 - 在交互式解释器(REPL)里试 await?→ 默认不支持;可用
await配合asyncio.get_event_loop().run_until_complete(...),但更推荐用python -m asyncio启动支持 await 的 REPL
await 一个已经完成的 Task 或 Future 会发生什么?
不会挂起,会立即返回结果,但仍是合法且安全的操作。这常用于“等某个后台任务收尾”,比如你提前 create_task 了一个长任务,之后在合适时机 await task 获取结果。
关键细节:
-
await已完成的Task不触发调度切换,开销极小,相当于一次属性访问(task.result()) - 但如果
Task被取消,await会抛出CancelledError,需捕获处理 - 不要反复
await同一个已完成Task—— 第二次 await 会直接返回缓存结果,但语义上容易引发逻辑混乱(比如误以为又执行了一次)
真正容易被忽略的是:await 的目标是否真的“需要等待”。比如 await asyncio.gather(a(), b()) 是并发等待,而 await a(); await b() 是串行。顺序和并发性不是由 await 决定的,是由你如何启动协程决定的。










