如何实现一个支持 yield from 的异步上下文管理器

10次阅读

async with 的 aenter__/__aexit 必须用 async def 定义并返回 Awaitable,不可用 yield from(会生成 AsyncGenerator 而违反协议);应改用 await 调用普通协程。

如何实现一个支持 yield from 的异步上下文管理器

python 3.7+ 中的异步上下文管理器(async with)**不支持直接在 __aenter____aexit__ 中使用 yield from**,因为这两个方法必须返回 Awaitable,而非生成器。你真正需要的是:用 async def 定义它们,并在内部 await 其他协程——而不是试图“委托”给另一个异步生成器。

为什么 yield from__aenter__/__aexit__ 中会报错

异步上下文管理器协议要求:

  • __aenter__ 必须是 async def 函数,返回一个可等待对象(如 await 后得到的结果)
  • __aexit__ 同样必须是 async def,返回 None 或一个真值(用于抑制异常)
  • yield from 出现在 async def 中,会使函数变成异步生成器(返回 AsyncGenerator),违反协议

常见错误现象:SyntaxError: 'yield from' inside async function(Python 3.6+ 默认禁止),或运行时报 TypeError: __aenter__ returned non-awaiter

正确写法:用 await 替代 yield from 委托协程

如果你原本想用 yield from some_async_iter() 来复用一段异步初始化/清理逻辑,实际应把那段逻辑封装为普通协程函数,然后在 __aenter__/__aexit__await 它。

import asyncio 

async def do_setup(): await asyncio.sleep(0.1) return "resource"

async def do_cleanup(res): await asyncio.sleep(0.1) print(f"cleaned {res}")

class AsyncResource: def init(self): self._res = None

async def __aenter__(self):     # ✅ 正确:await 协程,不是 yield from     self._res = await do_setup()     return self._res  async def __aexit__(self, exc_type, exc_val, exc_tb):     if self._res is not None:         await do_cleanup(self._res)

使用方式不变:async with AsyncResource() as res:

如果必须复用异步生成器逻辑,需手动展开

假设你有一个异步生成器 async def setup_steps(),想把它“展开”进 __aenter__。不能 yield from,但可以显式迭代并 await 每个 anext()

async def setup_steps():     await asyncio.sleep(0.05)     yield "step1"     await asyncio.sleep(0.05)     yield "step2" 

class AsyncResourceWithSteps: async def aenter(self): agen = setup_steps() try:

手动取第一个值(模拟“进入”时完成初始化)

        self._step = await anext(agen)         return self._step     except StopAsyncIteration:         raise RuntimeError("setup_steps yielded no values")  async def __aexit__(self, *args):     # 注意:无法自动继续迭代剩余步骤 —— 异步生成器状态已丢失     # 若需完整流程,应在 enter 中一次性 await 全部,或改用普通协程     pass

⚠️ 这种写法脆弱:异步生成器无法跨 __aenter__/__aexit__ 恢复状态;__aexit__ 无法访问同一个 agen 实例。实际项目中应避免。

最易被忽略的一点:异步上下文管理器的生命周期是单次、线性的,它不提供“暂停-恢复”能力;所谓“委托”,本质是组合协程,不是委托生成器。别让 yield from 的直觉误导你去写语法非法或语义断裂的代码。

text=ZqhQzanResources