Python await 关键字的真实作用

8次阅读

await 不是无条件让出控制权的魔法,而是调用右值的__await__方法获取迭代器并推进;若对象已就绪则立即返回,否则交还事件循环;仅限async函数内使用,语法编译时报错。

Python await 关键字的真实作用

await 不是“让出控制权”的魔法

它只是告诉 python:当前协程必须暂停,直到右边的 awaitable 对象(比如 async def 函数返回的 coroutineTaskFuture)完成;暂停后控制权交还事件循环,但**不是无条件让出**——如果右边对象已经就绪(例如刚创建的已完成 Future),await 会立刻返回结果,不切换上下文。

常见误解是“await 一定会 yield”,其实它底层调用的是对象的 __await__() 方法,本质是迭代一个生成器(返回 iterator),而事件循环只在迭代中途遇到 yield 才真正挂起。

await 只能在 async 函数里用,且不能嵌套在普通函数中

这是语法硬限制,不是运行时检查。Python 解释器在编译阶段就报错:SyntaxError: 'await' outside async function

容易踩的坑:

立即学习Python免费学习笔记(深入)”;

  • 在同步工具函数(如 Logging.debug()print())里试图 await 异步结果——必须把整个调用链升为 async
  • 误以为 Threading.Threadmultiprocessing.Process 内能直接用 await——它们运行在独立线程/进程,没有事件循环上下文
  • __init__ 中写 await——构造函数不能是 async,需改用工厂函数(如 async def create_instance(): ...

await 的实际开销:一次属性查找 + 一次迭代器推进

每次 await 表达式执行,Python 会:

  • 检查右值是否实现了 __await__(或继承Awaitable
  • 调用它拿到迭代器
  • 对迭代器调用 send(None),直到它 yield 或抛出 StopIteration

这意味着:

  • 对已结束的 coroutineFutureawait 是纯同步操作,耗时纳秒级
  • 对真正需要挂起的(如 await asyncio.sleep(1)),开销主要在事件循环调度,而非 await 本身
  • await 不等价于 yield from:后者可委托任意迭代器,await 只接受明确标记为 awaitable 的对象

别拿 await 当同步代码的“装饰”来用

比如写 await some_sync_function() 是非法的,因为同步函数不返回 awaitable;强行包装成 await asyncio.to_thread(some_sync_function) 也不是“让同步变快”,只是把它丢进线程池避免阻塞事件循环。

更隐蔽的问题:

  • 在 CPU 密集型逻辑里滥用 await asyncio.sleep(0) 企图“让出时间片”——这不会提升吞吐,反而增加调度负担
  • await 包裹未 await 的协程(如 await my_async_func 而非 await my_async_func())——会触发 RuntimeWarning: coroutine 'xxx' was never awaited,且该协程根本不会执行

真正决定异步行为的,是对象是否实现了 __await__,以及事件循环是否在运行;await 只是那个“触发点”,不是开关,也不是胶水。

text=ZqhQzanResources