大伙平常在读写文件之际肯定都见识过 with open() , 这便是最为经典的上下文管理器。在本节课当中要将 with 的底层原理彻彻底底弄明白, 学会自行去封装资源管理器, 这是开发项目时必须要掌握的知识点。
一、为什么需要with语句?
日常读取文件,传统写法:
data = f.read()
f.close()
问题: 要是read()在进行当中出现报错情况, 代码会发生中断, close()就不会被执行了, 且文件资源无法被释放出来, 从而造成资源泄露了。
于是有人用 try... 解决:
f = open("test.txt", "r", ="utf-8")
try:
data = f.read()
:
# 无论正常运行还是报错,一定会执行关闭
f.close()
代码冗长,重复度极高。
通过with语句, 其本质实际上就是try -的语法糖现象, 能够给我们自动完成释放资源的操作。
标准推荐写法:
data = f.read()
# 离开代码块,自动执行close(),无需手动关闭
二、上下文管理器协议(核心原理)
一个对象能够被 with 调用,必须实现两个魔法方法:
1. (self)
进入with代码块时自动执行,返回值赋值给 as 后的变量
2. (self, , , )
带有代码块一旦离开, 就会自动执行。不管是正常结束, 还是代码出现异常, 都会将其触发, 而这样是用来清理资源的。
案例:自定义计时器管理器
time
class Timer:
def (self):
self.start = time.()
print("开始执行任务")
self
def (self, , , ):
end = time.()
首先, 打印格式化字符串, 其中包含“任务结束”, 以及“耗时: ”, 再加上结束时间与开始时间相减后保留两位小数的结果, 以“秒”为单位。
# 使用
with Timer():
time.sleep(1.2)
print("正在运行业务代码")
运行观察:不管内部代码是否报错,最终都会打印耗时。
补充: 三个参数:异常类型、异常对象、异常堆栈。
要是方法返回True, 那么代码块内抛出的异常会被屏蔽;要是返回False, 异常将继续向外抛出。
三、简化写法:装饰器
每一次去书写类以及魔法方法的时候, 都是比较麻烦的, 内置了点, 仅仅只是使用生成器来快速对上下文管理器进行创建。
语法规则:
- yield之前:对应 逻辑
- yield之后:对应 清理逻辑
- yield的值,就是as接收的变量
from
time
@
def timer():
start = time.()
try:
yield # 交出控制权,执行with内部代码
:
end = time.()
把end减去start的结果, 保留两位小数, 以秒为单位, 用格式化字符串的写法, 输出“执行耗时: ”加上这个结果, 然后打印出来。
# 调用
with timer():
time.sleep(0.8)
print("测试代码")
改造,支持向外传递变量:
from
@
def (name):
print(f"【进入】{name}")
yield f"传入参数:{name}"
print(f"【退出】{name}")
with ("数据处理") as val:
print(val)
print("执行业务逻辑")
四、实际开发常用场景
1. 文件读写(最常用)
f.write("hello ")
2. 数据库连接:自动关闭连接、事务回滚
3. 线程锁:自动释放锁
4. 临时修改环境、临时切换目录,执行完毕自动恢复
5. 请求会话管理
五、多个with嵌套写法
方式1:连续书写(推荐)
使用打开文件操作, 以“a.txt”为文件名作为文件对象f1打开, 同时, 以“b.txt”为文件名作为文件对象f2打开。
print(f1.read(), f2.read())
方式2:多层缩进
with open("a.txt") as f1:
with open("b.txt") as f2:
pass
六、常见坑点
1. 切勿于with的内部情形之下, 对as所指示的变量进行再次赋值这一行为, 否则将会致使资源不能够依照正常的状况予以释放。
2. yield过后的代码务必得编写于内部之中, 以此确保在发生异常的状况下依旧能够执行清理操作。
3. 上下文管理器只作用于缩进内代码,退出缩进立刻清理资源
4. 猎取异常之时, 不得随意以True去遮蔽失误, 因其极易将程序漏洞隐匿起来。
七、课后练习
1. 以类的形式, 撰一个文件开启管理器, 自行开启、关上文件。
2. 借由 @ 去达成一个数据库模拟连接管理器, 当进入之时打印连接成功, 在退出之际打印关闭连接。
3. 思考:对比手动try-和with,分别适合什么场景