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_type、exc_value、traceback,即使你什么都不做也得声明它们 - 如果
__exit__返回True,会抑制异常;返回None或False则异常照常抛出
如何让自定义上下文管理器支持异常传播与部分抑制
关键在 __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__仍会被调用
实际用起来最难的不是写两个方法,而是预判所有退出路径——正常结束、return、break、各种异常、甚至 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()方法的使用


