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
<p>async def do_setup():
await asyncio.sleep(0.1)
return "resource"</p><p>async def do_cleanup(res):
await asyncio.sleep(0.1)
print(f"cleaned {res}")</p><p>class AsyncResource:
def <strong>init</strong>(self):
self._res = None</p><pre class='brush:python;toolbar:false;'>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"
<p>class AsyncResourceWithSteps:
async def <strong>aenter</strong>(self):
agen = setup_steps()
try:</p><h1>手动取第一个值(模拟“进入”时完成初始化)</h1><pre class='brush:python;toolbar:false;'> 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 的直觉误导你去写语法非法或语义断裂的代码。










