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

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 的直觉误导你去写语法非法或语义断裂的代码。