深入解析Python上下文管理器与contextlib

举报
柠檬🍋 发表于 2026/02/26 20:25:10 2026/02/26
【摘要】 深入解析Python上下文管理器与contextlib上下文管理器是Python中管理资源的重要机制,它确保资源在使用后被正确释放。本文将深入解析上下文管理器的工作原理和contextlib模块的高级用法。 上下文管理器基础上下文管理器通过__enter__和__exit__方法实现,配合with语句使用,确保资源的正确获取和释放。 上下文管理器核心实现"""Python上下文管理器与co...

深入解析Python上下文管理器与contextlib

上下文管理器是Python中管理资源的重要机制,它确保资源在使用后被正确释放。本文将深入解析上下文管理器的工作原理和contextlib模块的高级用法。

上下文管理器基础

上下文管理器通过__enter____exit__方法实现,配合with语句使用,确保资源的正确获取和释放。

上下文管理器核心实现

"""
Python上下文管理器与contextlib深入解析
包含自定义上下文管理器、contextlib工具、异步上下文等
"""

import time
import threading
import logging
from typing import Optional, Type, Any
from contextlib import (
    contextmanager, closing, suppress, redirect_stdout,
    redirect_stderr, ExitStack, asynccontextmanager
)
from io import StringIO
import asyncio

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


# ============ 基础上下文管理器 ============
class DatabaseConnection:
    """数据库连接上下文管理器"""
    
    def __init__(self, connection_string: str):
        self.connection_string = connection_string
        self.connection = None
        self.is_connected = False
    
    def __enter__(self):
        """进入上下文"""
        logger.info(f"连接到数据库: {self.connection_string}")
        self.connection = {"status": "connected", "id": id(self)}
        self.is_connected = True
        time.sleep(0.1)  # 模拟连接时间
        logger.info("数据库连接成功")
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出上下文"""
        if self.is_connected:
            logger.info("关闭数据库连接")
            self.connection = None
            self.is_connected = False
        
        # 处理异常
        if exc_type:
            logger.error(f"发生异常: {exc_val}")
            # 返回True表示异常已处理,不再传播
            return False


class FileLock:
    """文件锁上下文管理器"""
    
    def __init__(self, filename: str):
        self.filename = filename
        self.lock = threading.Lock()
        self.acquired = False
    
    def __enter__(self):
        logger.info(f"尝试获取文件锁: {self.filename}")
        self.lock.acquire()
        self.acquired = True
        logger.info(f"文件锁已获取: {self.filename}")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.acquired:
            self.lock.release()
            logger.info(f"文件锁已释放: {self.filename}")


class Timer:
    """计时器上下文管理器"""
    
    def __init__(self, name: str = "Operation"):
        self.name = name
        self.start_time = None
        self.elapsed = None
    
    def __enter__(self):
        self.start_time = time.time()
        logger.info(f"[{self.name}] 开始计时")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.elapsed = time.time() - self.start_time
        logger.info(f"[{self.name}] 耗时: {self.elapsed:.4f}秒")


class Transaction:
    """事务上下文管理器"""
    
    def __init__(self, db_connection):
        self.db = db_connection
        self.committed = False
    
    def __enter__(self):
        logger.info("开始事务")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            logger.info("事务回滚")
            self.rollback()
        else:
            if not self.committed:
                self.commit()
    
    def commit(self):
        logger.info("提交事务")
        self.committed = True
    
    def rollback(self):
        logger.info("回滚事务")


# ============ 使用contextmanager装饰器 ============
@contextmanager
def managed_resource(resource_name: str):
    """管理资源的上下文管理器"""
    logger.info(f"获取资源: {resource_name}")
    resource = {"name": resource_name, "status": "active"}
    try:
        yield resource
    finally:
        logger.info(f"释放资源: {resource_name}")


@contextmanager
def temporary_attribute(obj, attr_name, value):
    """临时修改对象属性"""
    old_value = getattr(obj, attr_name, None)
    setattr(obj, attr_name, value)
    try:
        yield
    finally:
        if old_value is None:
            delattr(obj, attr_name)
        else:
            setattr(obj, attr_name, old_value)


@contextmanager
def suppress_exceptions(*exceptions):
    """抑制指定异常"""
    try:
        yield
    except exceptions as e:
        logger.warning(f"抑制异常: {e}")


@contextmanager
def retry_on_failure(max_attempts: int = 3, delay: float = 1.0):
    """失败重试上下文"""
    for attempt in range(1, max_attempts + 1):
        try:
            yield attempt
            break
        except Exception as e:
            logger.warning(f"第{attempt}次尝试失败: {e}")
            if attempt == max_attempts:
                raise
            time.sleep(delay)


# ============ ExitStack高级用法 ============
def demonstrate_exit_stack():
    """演示ExitStack"""
    print("\n" + "="*60)
    print("ExitStack演示")
    print("="*60)
    
    with ExitStack() as stack:
        # 动态管理多个上下文
        files = []
        for i in range(3):
            # 模拟打开文件
            ctx = managed_resource(f"resource_{i}")
            resource = stack.enter_context(ctx)
            files.append(resource)
        
        logger.info(f"管理的资源: {[f['name'] for f in files]}")
        
        # 可以动态添加回调
        stack.callback(logger.info, "ExitStack清理完成")
    
    print("所有资源已清理")


# ============ suppress用法 ============
def demonstrate_suppress():
    """演示suppress"""
    print("\n" + "="*60)
    print("suppress演示")
    print("="*60)
    
    # 抑制特定异常
    with suppress(FileNotFoundError):
        with open("nonexistent_file.txt", "r") as f:
            content = f.read()
    
    print("FileNotFoundError被抑制,程序继续执行")
    
    # 抑制多个异常
    with suppress(ZeroDivisionError, ValueError):
        result = 1 / 0
        number = int("not_a_number")
    
    print("多个异常被抑制")


# ============ redirect_stdout用法 ============
def demonstrate_redirect():
    """演示输出重定向"""
    print("\n" + "="*60)
    print("输出重定向演示")
    print("="*60)
    
    # 捕获stdout
    output = StringIO()
    with redirect_stdout(output):
        print("这条消息被重定向到StringIO")
        print("这是第二条消息")
    
    captured = output.getvalue()
    print(f"捕获的输出:\n{captured}")


# ============ closing用法 ============
class Resource:
    """需要关闭的资源"""
    def __init__(self, name):
        self.name = name
        self.closed = False
    
    def close(self):
        logger.info(f"关闭资源: {self.name}")
        self.closed = True
    
    def do_something(self):
        if self.closed:
            raise RuntimeError("资源已关闭")
        return f"使用资源: {self.name}"


def demonstrate_closing():
    """演示closing"""
    print("\n" + "="*60)
    print("closing演示")
    print("="*60)
    
    with closing(Resource("test_resource")) as resource:
        result = resource.do_something()
        logger.info(result)
    
    print(f"资源已关闭: {resource.closed}")


# ============ 异步上下文管理器 ============
class AsyncDatabaseConnection:
    """异步数据库连接"""
    
    def __init__(self, dsn: str):
        self.dsn = dsn
        self.connection = None
    
    async def __aenter__(self):
        logger.info(f"异步连接到: {self.dsn}")
        await asyncio.sleep(0.1)
        self.connection = {"dsn": self.dsn, "status": "connected"}
        return self.connection
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        logger.info("异步关闭连接")
        self.connection = None


@asynccontextmanager
async def async_managed_resource(name: str):
    """异步资源管理器"""
    logger.info(f"异步获取资源: {name}")
    resource = {"name": name}
    try:
        yield resource
    finally:
        await asyncio.sleep(0.05)
        logger.info(f"异步释放资源: {name}")


async def demonstrate_async_context():
    """演示异步上下文管理器"""
    print("\n" + "="*60)
    print("异步上下文管理器演示")
    print("="*60)
    
    async with AsyncDatabaseConnection("postgresql://localhost/db") as conn:
        logger.info(f"使用连接: {conn}")
    
    async with async_managed_resource("async_resource") as resource:
        logger.info(f"使用资源: {resource}")


# ============ 综合示例 ============
class ConfigManager:
    """配置管理器 - 展示上下文管理器的实际应用"""
    
    def __init__(self):
        self.config = {}
        self._backup = None
    
    @contextmanager
    def temporary_config(self, **kwargs):
        """临时配置上下文"""
        self._backup = self.config.copy()
        self.config.update(kwargs)
        logger.info(f"临时配置: {kwargs}")
        try:
            yield self.config
        finally:
            self.config = self._backup
            self._backup = None
            logger.info("配置已恢复")
    
    @contextmanager
    def atomic_update(self):
        """原子更新上下文"""
        backup = self.config.copy()
        try:
            yield self
            logger.info("配置原子更新成功")
        except Exception as e:
            self.config = backup
            logger.error(f"配置更新失败,已回滚: {e}")
            raise


def main():
    """主函数"""
    print("="*60)
    print("Python上下文管理器与contextlib深入解析")
    print("="*60)
    
    # 1. 基础上下文管理器
    print("\n【基础上下文管理器演示】")
    with DatabaseConnection("postgresql://localhost/mydb") as conn:
        logger.info(f"使用连接: {conn}")
    
    # 2. 文件锁
    print("\n【文件锁演示】")
    with FileLock("data.txt"):
        logger.info("执行受保护的操作")
    
    # 3. 计时器
    print("\n【计时器演示】")
    with Timer("数据处理"):
        time.sleep(0.2)
    
    # 4. 事务
    print("\n【事务演示】")
    with Transaction({"name": "test_db"}) as tx:
        logger.info("执行业务操作")
        tx.commit()
    
    # 5. contextmanager装饰器
    print("\n【contextmanager装饰器演示】")
    with managed_resource("database_pool") as resource:
        logger.info(f"使用资源: {resource}")
    
    # 6. 临时属性修改
    print("\n【临时属性修改演示】")
    class MyClass:
        value = 10
    
    obj = MyClass()
    logger.info(f"原始值: {obj.value}")
    with temporary_attribute(obj, "value", 100):
        logger.info(f"临时值: {obj.value}")
    logger.info(f"恢复后: {obj.value}")
    
    # 7. 异常抑制
    demonstrate_suppress()
    
    # 8. 输出重定向
    demonstrate_redirect()
    
    # 9. closing
    demonstrate_closing()
    
    # 10. ExitStack
    demonstrate_exit_stack()
    
    # 11. 配置管理器
    print("\n【配置管理器演示】")
    config_mgr = ConfigManager()
    config_mgr.config = {"debug": False, "timeout": 30}
    
    with config_mgr.temporary_config(debug=True, timeout=60):
        logger.info(f"临时配置: {config_mgr.config}")
    
    logger.info(f"恢复后配置: {config_mgr.config}")
    
    # 12. 异步上下文
    asyncio.run(demonstrate_async_context())
    
    print("\n" + "="*60)
    print("上下文管理器总结")
    print("="*60)
    print("1. __enter__/__exit__: 基础实现方式")
    print("2. @contextmanager: 简化实现")
    print("3. ExitStack: 动态管理多个上下文")
    print("4. suppress: 抑制特定异常")
    print("5. redirect_stdout: 重定向输出")
    print("6. closing: 确保close被调用")
    print("7. 异步上下文: async with支持")
    print("="*60)


if __name__ == "__main__":
    main()

上下文管理器执行流程

True
False
进入with语句
调用__enter__
获取资源
执行with块代码
是否发生异常?
调用__exit__
传入异常信息
调用__exit__
无异常
__exit__返回值
抑制异常
传播异常

关键要点

  1. 资源管理:确保资源正确获取和释放
  2. 异常处理:在__exit__中处理或传播异常
  3. 简化实现:使用@contextmanager装饰器
  4. 组合使用ExitStack管理多个上下文
  5. 异步支持async with@asynccontextmanager

上下文管理器是Python资源管理的最佳实践,合理使用可以显著提升代码的健壮性和可读性。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。