导航菜单

  • 1.什么是MCP
  • 2.MCP架构
  • 3.MCP服务器
  • 4.MCP客户端
  • 5.版本控制
  • 6.连接MCP服务器
  • 7.SDKs
  • 8.Inspector
  • 9.规范
  • 10.架构
  • 11.协议
  • 12.生命周期
  • 13.工具
  • 14.资源
  • 15.提示
  • 16.日志
  • 17.进度
  • 18.传输
  • 19.补全
  • 20.引导
  • 21.采样
  • 22.任务
  • 23.取消
  • 24.Ping
  • 25.根
  • 26.分页
  • 27.授权
  • 28.初始化
  • 29.工具
  • 30.资源
  • 31.结构化输出
  • 32.提示词
  • 33.上下文
  • 34.StreamableHTTP
  • 35.参数补全
  • 36.引导
  • 37.采样
  • 38.LowLevel
  • 39.任务
  • 40.取消
  • 41.ping
  • 42.根
  • 43.分页
  • 44.授权
  • 45.FunctionCalling
  • starlette
  • FastAPI
  • Keycloak
  • asyncio
  • contextlib
  • httpx
  • pathlib
  • pydantic
  • queue
  • subprocess
  • threading
  • uvicorn
  • JSON-RPC
  • LiteLLM
  • pydantic-settings
  • agentback
  • 0
  • 1. 什么是上下文管理器?
    • 1.1 什么是 with 语句?
    • 1.2 什么是上下文管理器?
  • 2. 什么是 contextlib?
  • 3. 用 @contextmanager 创建上下文管理器
    • 3.1 基本用法
    • 3.2 计时器
    • 3.3 临时修改目录
  • 4. closing:自动关闭有 close() 的对象
  • 5. suppress:忽略指定异常
    • 5.1 不用 suppress 的写法
    • 5.2 用 suppress 的写法
  • 6. nullcontext:什么都不做的占位符
  • 7. 带锁的上下文管理器
  • 8. 注意事项
  • 9. 总结

1. 什么是上下文管理器? #

1.1 什么是 with 语句? #

with 语句用于自动管理资源:进入时做"准备工作",退出时(无论正常结束还是异常)做"清理工作"。最常见的是打开文件:

# 用 with 打开文件,退出 with 块时自动关闭文件
with open("file.txt", "r", encoding="utf-8") as f:
    content = f.read()
# 这里文件已经自动关闭,无需手动 f.close()

不用 with 时,需要自己 try/finally 确保关闭,容易遗漏。

1.2 什么是上下文管理器? #

上下文管理器是实现了 __enter__ 和 __exit__ 方法的对象,可以和 with 配合使用。open() 返回的文件对象就是一个上下文管理器。

contextlib 的作用:不用手写 __enter__ 和 __exit__,用生成器或工具函数就能快速创建上下文管理器,代码更简洁。

通俗比喻:with 像"进门拿钥匙、出门还钥匙"的流程,上下文管理器就是定义"进门做什么、出门做什么"的规则,contextlib 帮你用更简单的方式写出这些规则。

2. 什么是 contextlib? #

contextlib 是 Python 标准库提供的模块,用于简化上下文管理器的编写,主要包含:

  • @contextmanager:用生成器定义上下文管理器,最常用
  • closing():包装有 close() 方法的对象,退出时自动调用
  • suppress():在 with 块内忽略指定异常

3. 用 @contextmanager 创建上下文管理器 #

3.1 基本用法 #

用 @contextmanager 装饰一个生成器函数,yield 之前的代码相当于 __enter__(进入时执行),yield 之后的代码相当于 __exit__(退出时执行)。

# 导入 contextmanager
from contextlib import contextmanager

# 用 @contextmanager 装饰生成器函数
@contextmanager
def my_context():
    # yield 之前的代码:进入 with 时执行(相当于 __enter__)
    print("进入 with,做准备工作")
    try:
        # yield 后面的值会赋给 as 后面的变量
        yield "你好"
        # yield 之后的代码:退出 with 时执行(相当于 __exit__)
    finally:
        # 用 finally 确保退出时一定会执行清理
        print("退出 with,做清理工作")

# 使用
with my_context() as msg:
    print("在 with 块内,收到的值:", msg)
    print("正在使用资源...")

# 这里已经退出 with,清理已完成
print("程序继续")

说明:yield 后面的 "你好" 会传给 as msg。无论 with 块内是否发生异常,finally 里的清理代码都会执行。

3.2 计时器 #

# 导入 contextmanager 和 time
from contextlib import contextmanager
import time

# 定义一个"计时"上下文管理器
@contextmanager
def timer():
    # 进入时记录开始时间
    start = time.perf_counter()
    try:
        yield
    finally:
        # 退出时计算并打印耗时
        elapsed = time.perf_counter() - start
        print(f"耗时: {elapsed:.3f} 秒")

# 使用:测量某段代码的执行时间
with timer():
    time.sleep(1)
    print("这段代码执行了约 1 秒")

3.3 临时修改目录 #

# 导入 contextmanager、os 和 pathlib
from contextlib import contextmanager
import os
from pathlib import Path

# 定义一个"临时切换目录"的上下文管理器
@contextmanager
def cd(path):
    # 进入时保存当前目录
    old_dir = os.getcwd()
    try:
        # 切换到新目录
        os.chdir(path)
        yield
    finally:
        # 退出时恢复原目录
        os.chdir(old_dir)

# 使用:在 with 块内切换到指定目录,退出后恢复
# 这里用当前目录下的子目录做演示(若不存在则创建)
demo_dir = Path("_contextlib_demo")
demo_dir.mkdir(exist_ok=True)
with cd(demo_dir):
    print("当前目录:", os.getcwd())
    # 在 with 块内,当前目录已切换
print("退出后目录:", os.getcwd())
# 清理演示目录
demo_dir.rmdir()

4. closing:自动关闭有 close() 的对象 #

有些对象有 close() 方法,但不是上下文管理器(没有 __enter__/__exit__)。用 closing() 包装后,退出 with 时会自动调用 close()。

# 导入 contextlib
from contextlib import closing

# 模拟一个"有 close 方法"的对象(如数据库连接、网络连接等)
class SimpleResource:
    def __init__(self):
        print("资源已打开")
    def close(self):
        print("资源已关闭")

# 用 closing 包装,退出 with 时自动调用 close()
with closing(SimpleResource()) as res:
    print("正在使用资源...")
print("with 已结束")

说明:urlopen() 返回的对象、某些数据库连接等也有 close() 方法,同样可以用 closing() 包装,确保退出时自动关闭。

5. suppress:忽略指定异常 #

有时你希望某段代码忽略某些异常(比如文件不存在时跳过)。

5.1 不用 suppress 的写法 #

# 删除文件,如果不存在会报错
import os

try:
    os.remove("不存在的文件.txt")
except FileNotFoundError:
    pass  # 忽略"文件不存在"错误

5.2 用 suppress 的写法 #

# 导入 contextlib 和 os
from contextlib import suppress
import os

# suppress(FileNotFoundError) 表示忽略 FileNotFoundError
# 退出 with 时如果发生该异常,不会抛出,静默忽略
with suppress(FileNotFoundError):
    os.remove("不存在的文件.txt")

# 程序正常继续,不会因为文件不存在而崩溃
print("程序继续执行")

说明:可以同时忽略多种异常,如 suppress(FileNotFoundError, PermissionError)。

6. nullcontext:什么都不做的占位符 #

nullcontext 是一个"空"的上下文管理器,什么都不做,适合作为占位符。可以传入一个值,该值会通过 as 返回。

# 导入 contextlib
from contextlib import nullcontext

# 不传参数时,as 得到 None
with nullcontext() as x:
    print("x =", x)

# 传参数时,as 得到该参数
with nullcontext(42) as value:
    print("value =", value)

说明:当某些情况下"不需要真正进入上下文"时,可以用 nullcontext() 占位,使代码结构统一。

7. 带锁的上下文管理器 #

结合 threading.Lock,可以封装一个"进入 with 时加锁、退出时解锁"的上下文管理器(其实 Lock 本身已经支持 with,这里仅作演示):

# 导入 contextlib 和 threading
from contextlib import contextmanager
import threading

# 创建一个锁
lock = threading.Lock()

# 用 @contextmanager 封装"加锁-解锁"逻辑
@contextmanager
def with_lock(lk):
    # 进入时加锁
    lk.acquire()
    try:
        yield
    finally:
        # 退出时解锁(无论是否异常)
        lk.release()

# 使用
with with_lock(lock):
    print("已获取锁,执行临界区代码")
    # 模拟一些操作
print("已释放锁")

说明:实际使用 threading.Lock 时,直接用 with lock: 即可,无需自己封装。这里只是演示 @contextmanager 的用法。

8. 注意事项 #

  1. 必须用 try/finally:@contextmanager 装饰的生成器中,yield 之后的清理代码应放在 finally 里,确保异常时也会执行。
  2. yield 只能一次:生成器里只能 yield 一次,yield 之前的代码是"进入"逻辑,之后是"退出"逻辑。
  3. suppress 只忽略指定异常:其他异常仍会正常抛出。

9. 总结 #

内容 要点
@contextmanager 用生成器创建上下文管理器,yield 前是进入逻辑,后是退出逻辑
closing() 包装有 close() 的对象,退出 with 时自动调用
suppress() 在 with 块内忽略指定异常
nullcontext() 空占位符,可选返回一个值

记忆口诀:写上下文管理器用 @contextmanager,有 close 用 closing,要忽略异常用 suppress。

← 上一节 asyncio 下一节 FastAPI →

访问验证

请输入访问令牌

Token不正确,请重新输入