登录
首页 >  文章 >  python教程

Python 中的 contextmanager 是用于同步上下文管理的,但如果你希望它支持异步操作,可以使用 asynccontextmanager 装饰器。这个装饰器是 Python 3.7 引入的,专门用于定义异步上下文管理器。示例代码:from contextlib import asynccontextmanager @asynccontextmanager async def asy

时间:2026-03-10 21:05:36 471浏览 收藏

Python 3.7+ 中实现真正的异步上下文管理需摒弃同步的 `@contextmanager`,转而使用专为协程设计的 `@asynccontextmanager` 装饰器或手动实现 `__aenter__`/`__aexit__` 的异步类——前者语法简洁、贴近开发者直觉,后者则提供最大灵活性与控制力;无论哪种方式,核心都在于确保资源的获取与释放均为可等待的异步操作,避免混用同步装饰器与异步函数导致的运行时错误,同时在实际开发中优先选用 `aiohttp`、`asyncpg` 等原生异步库,或借助线程池适配阻塞调用,从而安全、高效地支撑 FastAPI 生命周期管理、数据库连接池、缓存初始化等典型异步场景。

如何让 contextmanager 支持异步上下文管理

Python 的 @contextmanager 装饰器本身不支持异步上下文管理(即不能直接用于 async with),因为它返回的是同步的生成器。要让上下文管理器支持 async with,必须使用 async def 定义,并实现 __aenter____aexit__ 方法——也就是自定义异步上下文管理器类。

用类实现异步上下文管理器

这是最标准、最可控的方式。你需要定义一个类,手动实现两个异步方法:

  • __aenter__:在 async with 进入时被 await,可执行异步初始化(如连接数据库、获取锁)
  • __aexit__:在 async with 结束时被 await,可执行异步清理(如关闭连接、提交/回滚事务)

示例:

class AsyncDBConnection:
    def __init__(self, url):
        self.url = url
        self.conn = None
<pre class="brush:php;toolbar:false"><code>async def __aenter__(self):
    self.conn = await aiomysql.connect(host=self.url)
    return self.conn

async def __aexit__(self, exc_type, exc_val, exc_tb):
    if self.conn:
        await self.conn.close()</code>

使用方式:

async with AsyncDBConnection("localhost") as conn:
    result = await conn.execute("SELECT 1")

用 asynccontextmanager(推荐)

Python 3.7+ 的 contextlib 提供了 @asynccontextmanager 装饰器,语法上最接近 @contextmanager,但专为异步设计。它把异步生成器自动包装成符合 AsyncContextManager 协议的对象。

注意:需从 contextlib 导入,不是 asyncio

from contextlib import asynccontextmanager
<p>@asynccontextmanager
async def lifespan(app):</p><h1>启动前:异步初始化</h1><pre class="brush:php;toolbar:false"><code>database = await connect_db()
cache = await init_cache()
try:
    yield {"db": database, "cache": cache}
finally:
    # 关闭后:异步清理
    await database.close()
    await cache.shutdown()</code>

使用方式相同:

async with lifespan(my_app) as resources:
    await do_something(resources["db"])

不能混用 sync 和 async

以下写法是错误的:

  • @contextmanager + async def —— 会报 TypeError: @contextmanager only works with sync generators
  • __aenter__ 中返回普通(非 awaitable)对象 —— 必须返回 awaitable,否则 async with 会失败
  • 忘记在 __aexit__ 中处理异常或加 await —— 清理逻辑不会真正执行

常见适配场景

很多同步库(如 requestssqlite3)没有原生异步支持。若想封装其资源为异步上下文管理器,需借助线程池或专用异步库:

  • 对阻塞 I/O 操作(如文件读写、HTTP 请求),可用 loop.run_in_executor 包装,再放入 @asynccontextmanager
  • 优先选用已有的异步生态库(如 aiohttpaiomysqlasyncpg),它们通常自带异步上下文管理器
  • FastAPI 的 Depends、Starlette 的 Lifespan 等都依赖 @asynccontextmanager 实现生命周期管理

今天带大家了解了的相关知识,希望对你有所帮助;关于文章的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>