Python async 函数的调用语义解析

3次阅读

async函数调用不执行而返回coroutine对象,必须用await(在async函数内)或asyncio.run()(在顶层)驱动;await只能在async函数中使用,asyncio.run()仅用于最外层入口。

Python async 函数的调用语义解析

async 函数不能直接调用,会返回 coroutine 对象

你写了个 async def fetch_data(),然后直接写 fetch_data() —— 这不会执行函数体,只会得到一个 <coroutine Object fetch_data at></coroutine>。这是最常被忽略的起点错误。

根本原因:pythonasync 函数是协程函数,调用它只是创建协程对象,不触发执行。必须用 await(在另一个 async 函数里)或 asyncio.run()(在顶层)来驱动。

  • async 函数内调用:用 await fetch_data()
  • 在普通函数或模块顶层调用:用 asyncio.run(fetch_data())
  • 误用 fetch_data() 后又传给 print()json.dumps(),会看到 coroutine 对象字符串,不是你想要的数据
  • asyncio.run() 每次调用都会新建事件循环,不能在已运行的 loop 中重复调用(比如 jupyterfastapi 生命周期里)

await 只能在 async 函数里用,否则 SyntaxError

await fetch_data() 却没把它包在 async def 里?Python 直接报错:SyntaxError: 'await' outside async function。这不是运行时错,是解析期就拦下来的。

常见场景包括:交互式终端(ipython)、脚本顶层、类方法没加 async、或者想在 __init__ 里 await —— 全都不行。

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

  • IPython/Jupyter 中可用 await fetch_data()(因为它们底层做了魔法处理),但纯 Python 脚本不行
  • 类方法要 await,必须声明为 async def method(self),且调用处也要 await obj.method()
  • __init__ 不能是 async,想初始化异步资源得用工厂函数(如 async def create_client(): return Client(await connect())

asyncio.run() 是入口,不是万能胶水

asyncio.run() 看起来方便,但它只该用在程序最外层,比如脚本入口。滥用会导致事件循环冲突、资源未清理、甚至死锁。

典型误用:在 Web 框架(如 flaskdjango)视图里调用 asyncio.run();或在 for 循环里反复调用它去发多个请求 —— 每次都启停 loop,开销大且不可靠。

  • Web 框架需匹配异步生命周期:FastAPI/Starlette 原生支持 async def 路由;Flask 则需插件或改用 Quart
  • 并发请求别写 [asyncio.run(fetch(i)) for i in range(10)],应改用 await asyncio.gather(*[fetch(i) for i in range(10)])
  • asyncio.run() 会自动关闭 loop,若你手动调了 asyncio.get_Event_loop(),再调它会报 RuntimeError: asyncio.run() cannot be called from a running event loop

await 表达式本身不“等待”,它让出控制权

await 不是“阻塞等结果”,而是告诉解释器:“我现在卡住了,你先去跑别的协程,等这东西 ready 了再叫我”。理解这点,才能避开“为什么加了 await 还慢”这类困惑。

性能关键点在于:await 的对象是否真能并发(比如 asyncio.sleep()aiohttp.ClientSession.get()),还是只是个包装了同步调用的假协程(比如忘了把 requests.get() 换成 aiohttp)。

  • await 一个 CPU 密集型任务(如 await asyncio.to_thread(math.factorial, 100000))才合理;直接 await time.sleep() 会阻塞整个 event loop
  • 第三方库是否真异步?查文档看它返回的是 coroutine 还是普通值;requests 全是同步的,aiohttp / httpx.AsyncClient 才是真异步
  • await 链过长(如 await f1(); await f2(); await f3())默认是串行,想并发得显式用 gathercreate_task

async 的语义边界很清晰:它不改变单次调用的逻辑,只改变调度方式。最容易被绕晕的地方,是把“写了 async”当成“自动变快”——其实只是给了你组织并发的工具,怎么用,还得自己画清依赖和等待点。

text=ZqhQzanResources