python之contextmanager

一、contextmanager有什么用?

contextmanager是 Python 标准库contextlib模块提供的同步上下文管理器装饰器,核心作用是简化同步上下文管理器的实现。

在没有contextmanager之前,实现一个上下文管理器需要手动定义类,并实现__enter__()和__exit__()两个魔法方法;而使用contextmanager,只需装饰一个同步生成器函数,即可快速封装出上下文管理器,无需手动实现魔法方法,代码更简洁优雅。

二、适用场景

当你在同步代码中需要管理「需要先获取、使用后必须释放 / 清理」的资源时,contextmanager是最优选择,

典型场景包括:

  • 同步文件操作:普通文件的打开 / 关闭,自动释放文件句柄(Python 内置open()本身就是上下文管理器,此处用于演示封装思想)
  • 同步数据库连接:如pymysql操作 MySQL,自动建立 / 关闭数据库连接
  • 同步锁 / 资源占用:如threading.Lock,自动获取 / 释放锁,避免死锁
  • 临时资源创建 / 清理:如临时目录创建、临时文件写入后自动删除、日志上下文切换等

简单来说:同步场景 + 资源需要自动管理(获取 + 释放),就用contextmanager。

三、核心使用规则

@contextmanager装饰的同步生成器函数,必须满足固定结构:

  • 函数用def定义(同步函数,无async关键字)
  • 函数内部包含且仅包含一个yield关键字(用于分割「资源获取」和「资源释放」逻辑)
  • 代码结构分为三部分:
    1. yield 之前:对应上下文管理器的__enter__()方法,负责获取 / 初始化资源,执行时机是进入with语句块时
    2. yield 关键字:将需要使用的资源「返回」给with语句的as变量(可返回单个资源,也可返回None),同时暂停函数执行,交出控制权给with语句块
    3. yield 之后:对应上下文管理器的__exit__()方法,负责释放 / 清理资源,执行时机是离开with语句块时(无论代码块正常结束还是发生异常,都会执行)
  • 推荐将yield之后的清理逻辑放在finally块中,确保资源释放的可靠性(即使中间代码抛出异常,清理逻辑也能执行)

四、代码示例

我们实现一个「同步文件读写工具」,使用contextmanager封装普通文件的打开 / 关闭,确保文件句柄自动释放,无需手动调用close(),与之前的异步示例形成对比,更易理解。

python 复制代码
# 1. 导入必要模块(contextmanager是Python内置,无需额外安装依赖)
from contextlib import contextmanager

# 2. 使用@contextmanager装饰同步生成器,实现同步上下文管理器
@contextmanager
def file_manager(file_path: str, mode: str = "r", encoding: str = "utf-8"):
    """
    同步文件管理器:自动打开/关闭文件,封装同步文件操作的资源管理
    :param file_path: 文件路径
    :param mode: 文件打开模式(r/w/a等)
    :param encoding: 文件编码
    """
    # 初始化文件句柄
    file_handle = None
    
    try:
        # -------- 第一步:yield之前(对应__enter__()):获取/初始化资源 --------
        file_handle = open(file_path, mode=mode, encoding=encoding)
        print(f"✅ 同步打开文件:{file_path}(模式:{mode})")
        
        # -------- 第二步:yield关键字:返回资源给with语句,暂停函数执行 --------
        yield file_handle  # 将文件句柄传递给with ... as 后的变量
    
    # -------- 第三步:yield之后(对应__exit__()):释放/清理资源(finally确保必执行) --------
    finally:
        if file_handle:
            file_handle.close()
            print(f"❌ 同步关闭文件:{file_path}")

# 3. 使用同步上下文管理器(配合with语句)
def main():
    # 测试文件路径
    test_file_path = "test_sync.txt"
    
    # 示例1:同步写入文件内容
    with file_manager(test_file_path, mode="w") as f:
        f.write("Hello, contextmanager!\n")
        f.write("这是同步文件写入测试\n")
    
    # 示例2:同步读取文件内容
    with file_manager(test_file_path, mode="r") as f:
        content = f.read()
        print("\n📄 文件内容:")
        print(content)

# 4. 运行同步主函数
if __name__ == "__main__":
    main()

关键解析

  • 进入with语句块时,自动执行yield之前的open()逻辑,打开文件并获取文件句柄
  • with语句块内操作文件句柄,执行完成后自动离开代码块,触发finally中的close()逻辑,关闭文件
  • 即使文件写入 / 读取过程中抛出异常(例如手动添加raise Exception("测试异常")),finally块中的清理逻辑依然会执行,确保文件句柄被释放,不会造成资源泄露

异常处理

python 复制代码
@contextmanager
def file_manager(file_path: str, mode: str = "r", encoding: str = "utf-8"):
    file_handle = None
    try:
        file_handle = open(file_path, mode=mode, encoding=encoding)
        print(f"✅ 同步打开文件:{file_path}(模式:{mode})")
        yield file_handle  # 此处抛出的异常会被传递到with代码块
    except FileNotFoundError as e:
        print(f"❌ 错误:文件不存在 - {e}")
        raise  # 可选:重新抛出异常,让调用方感知并处理
    except Exception as e:
        print(f"❌ 操作文件出错:{e}")
        raise
    finally:
        if file_handle:
            file_handle.close()
            print(f"❌ 同步关闭文件:{file_path}")

扩展示例

演示contextmanager用于管理临时目录(创建→使用→自动删除),更贴近实际项目中的资源管理场景:

python 复制代码
import os
import shutil
from contextlib import contextmanager

@contextmanager
def temporary_directory(dir_path: str = "temp_dir"):
    """同步临时目录管理器:自动创建→使用→删除临时目录"""
    # 1. 初始化:创建临时目录
    try:
        os.makedirs(dir_path, exist_ok=True)
        print(f"✅ 成功创建临时目录:{dir_path}")
        yield dir_path  # 返回临时目录路径给with语句
    finally:
        # 2. 清理:删除临时目录及其内容
        if os.path.exists(dir_path):
            shutil.rmtree(dir_path)
            print(f"❌ 成功删除临时目录:{dir_path}")

# 使用临时目录
def test_temp_dir():
    with temporary_directory() as temp_dir:
        # 在临时目录中创建文件
        temp_file = os.path.join(temp_dir, "test.txt")
        with open(temp_file, "w") as f:
            f.write("临时文件内容")
        print(f"📄 临时文件已创建:{temp_file},文件大小:{os.path.getsize(temp_file)} 字节")

if __name__ == "__main__":
    test_temp_dir()
相关推荐
毕设源码-钟学长15 小时前
【开题答辩全过程】以 基于Python的车辆管理系统为例,包含答辩的问题和答案
开发语言·python
CCPC不拿奖不改名15 小时前
数据处理与分析:数据可视化的面试习题
开发语言·python·信息可视化·面试·职场和发展
液态不合群15 小时前
线程池和高并发
开发语言·python
旦莫16 小时前
Pytest教程:Pytest与主流测试框架对比
人工智能·python·pytest
数据大魔方16 小时前
【期货量化实战】螺纹钢量化交易指南:品种特性与策略实战(TqSdk完整方案)
python·算法·github·程序员创富·期货程序化·期货量化·交易策略实战
旻璿gg16 小时前
paddleocr、paddleocrvl、ppocrv5
python
清水白石00816 小时前
手写超速 CSV 解析器:利用 multiprocessing 与 mmap 实现 10 倍 Pandas 加速
python·pandas
Corleo16 小时前
记录一次复杂的 ONNX 到 TensorRT 动态 Shape 转换排错过程
python·ai
shughui17 小时前
Python基础面试题:语言定位+数据类型+核心操作+算法实战(含代码实例)
开发语言·python·算法