Python 上下文管理器的自定义实现

发布时间 - 2026-01-30 00:00:00    点击率:
__enter__和__exit__必须成对出现,因为with语句依赖二者完成资源获取与清理的完整生命周期;缺__exit__会报AttributeError,且无法保证异常路径下资源释放。

为什么 __enter____exit__ 必须成对出现

因为 with 语句的底层机制依赖这两个方法构成完整生命周期:进入时调用 __enter__ 获取资源,退出时无条件触发 __exit__ 做清理。缺一不可,否则会报 AttributeError: __exit__

常见错误是只写了 __enter__(比如想简单返回个值),结果 with 块结束时找不到退出逻辑,资源泄漏或异常被吞掉。

  • __enter__ 应该返回要绑定到 as 变量的对象(可以是 self,也可以是其他实例)
  • __exit__ 必须接收三个参数:exc_typeexc_valuetraceback,即使你什么都不做也得声明它们
  • 如果 __exit__ 返回 True,会抑制异常;返回 NoneFalse 则异常照常抛出

如何让自定义上下文管理器支持异常传播与部分抑制

关键在 __exit__ 的返回值逻辑。不是“捕获异常”,而是“决定是否让异常继续向上冒泡”。比如日志记录后仍要抛出,就不能 return True。

典型场景:打开文件写入,希望 IO 错误暴露给调用方,但临时锁必须释放。

def __exit__(self, exc_type, exc_value, traceback):
    self.lock.release()  # 总要释放
    if exc_type is not None and issubclass(exc_type, ValueError):
        print("忽略 ValueError")
        return True  # 抑制这一类
    return False  # 其他异常照常抛出
  • exc_type is not None 判断是否有异常发生
  • issubclass(exc_type, SomeException) 精确匹配类型,避免用字符串比较
  • 不要在 __exit__ 里主动 raise 新异常——这会覆盖原始异常,极难调试

contextlib.contextmanager 装饰器替代手写类的适用边界

当逻辑简单、无状态、不需复用实例时,@contextmanager 更轻量;但一旦涉及属性缓存、多次进入、或需要继承扩展,就必须用类。

例如临时切换工作目录,用函数式写法很干净:

from contextlib import contextmanager
import os

@contextmanager def cd(path): old = os.getcwd() os.chdir(path) try: yield finally: os.chdir(old)

  • yield 之前的部分相当于 __enter__,之后(finally 块)相当于 __exit__
  • 不能在 @contextmanager 函数里返回值给

    as
    绑定——除非用 yield value,且该值会在 with 块中可用
  • 装饰器版本无法被继承,也不支持 isinstance(obj, ContextManager) 检查(它只是个 generator function)

测试自定义上下文管理器是否真正“安全退出”

不能只测正常流程,重点验证异常路径下资源是否释放。比如文件句柄、网络连接、数据库事务等,漏掉异常分支就等于埋雷。

最直接的方式是故意抛异常并检查状态:

with MyResource() as r:
    assert r.is_open
    raise KeyError("boom")
# 此处断言 r.is_open == False,否则 __exit__ 没生效
  • pytest.raises 包裹整个 with 块,再检查退出后状态
  • 避免在 __exit__ 里做耗时操作(如网络请求),它可能在异常处理链中被多次调用
  • 如果上下文对象本身有状态(如计数器、缓存),确保 __exit__ 不依赖未初始化的属性——__enter__ 失败时 __exit__ 仍会被调用

实际用起来最难的不是写两个方法,而是预判所有退出路径——正常结束、returnbreak、各种异常、甚至 os._exit()。只要没覆盖全,就存在资源残留风险。


# python  # ai  # 为什么  # pytest  # break  # 字符串  # 继承  # raise  # finally  # function  # 对象  # 数据库  # 会报  # 抛出  # 能在  # 自定义  # 管理器  # 绑定  # 返回值  # 是个  # 也不  # 句柄 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 详解Nginx + Tomcat 反向代理 负载均衡 集群 部署指南  Java解压缩zip - 解压缩多个文件或文件夹实例  C++用Dijkstra(迪杰斯特拉)算法求最短路径  iOS中将个别页面强制横屏其他页面竖屏  VIVO手机上del键无效OnKeyListener不响应的原因及解决方法  Laravel如何使用Passport实现OAuth2?(完整配置步骤)  Laravel怎么实现API接口鉴权_Laravel Sanctum令牌生成与请求验证【教程】  购物网站制作费用多少,开办网上购物网站,需要办理哪些手续?  夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化  大连网站制作公司哪家好一点,大连买房网站哪个好?  Laravel Session怎么存储_Laravel Session驱动配置详解  Laravel怎么实现模型属性的自动加密  如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框  如何确保西部建站助手FTP传输的安全性?  如何在万网开始建站?分步指南解析  Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全  HTML 中动态设置元素 name 属性的正确语法详解  再谈Python中的字符串与字符编码(推荐)  Laravel Seeder填充数据教程_Laravel模型工厂Factory使用  如何批量查询域名的建站时间记录?  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  用yum安装MySQLdb模块的步骤方法  Python图片处理进阶教程_Pillow滤镜与图像增强  html文件怎么打开证书错误_https协议的html打开提示不安全【指南】  Laravel怎么配置不同环境的数据库_Laravel本地测试与生产环境动态切换【方法】  PHP 实现电台节目表的智能时间匹配与今日/明日轮播逻辑  如何在万网自助建站平台快速创建网站?  Laravel如何实现本地化和多语言支持?(i18n教程)  深圳网站制作平台,深圳市做网站好的公司有哪些?  想要更高端的建设网站,这些原则一定要坚持!  头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?  Swift中swift中的switch 语句  JavaScript Ajax实现异步通信  如何快速搭建FTP站点实现文件共享?  高端智能建站公司优选:品牌定制与SEO优化一站式服务  进行网站优化必须要坚持的四大原则  深圳防火门网站制作公司,深圳中天明防火门怎么编码?  Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程  东莞专业网站制作公司有哪些,东莞招聘网站哪个好?  jimdo怎样用html5做选项卡_jimdo选项卡html5实现与切换效果【指南】  Laravel如何实现本地化和多语言支持_Laravel多语言配置与翻译文件管理  北京网站制作的公司有哪些,北京白云观官方网站?  Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑  香港服务器WordPress建站指南:SEO优化与高效部署策略  Laravel Eloquent关联是什么_Laravel模型一对一与一对多关系精讲  大学网站设计制作软件有哪些,如何将网站制作成自己app?  智能起名网站制作软件有哪些,制作logo的软件?  如何在IIS中新建站点并解决端口绑定冲突?  网站制作大概要多少钱一个,做一个平台网站大概多少钱?  详解jQuery停止动画——stop()方法的使用