视频链接:
不如语冰
https://space.bilibili.com/70431433?spm_id_from=333.1007.0.0
文章 代码链接:
https://github.com/zyf-ngu/Qmatter
7 上下文管理与with语句
上下文管理是一种用于管理资源(如文件、网络连接、锁等)的机制,它确保在代码块执行前后进行正确的资源分配和释放,即使代码块中发生了异常也是如此。 在Python中,上下文管理是通过with语句来实现的。
with语句允许我们定义一个执行上下文,在该上下文中,我们可以确保资源的获取和释放是自动完成的。 即在with语句中,我们只需要关注资源或对象本身的操作(这点和普通方法一致),系统自动释放资源和对象(这点在普通方法中需要通过try except和finally实现,即try实现功能,except处理异常,finally释放资源。)
上下文管理器的基本结构
上下文管理的基本结构由两个特殊方法组成:enter__和__exit。这两个方法定义在上下文管理器对象中。
- enter(self):
当进入with语句块时,会调用这个方法。该方法返回的资源(是类吗?)会被赋值给as后面的变量(如果有的话)。 通常在这个方法中获取资源(如打开文件)。
- exit(self, exc_type, exc_value, traceback)`:
当离开with`语句块时(无论是正常结束还是因为异常),都会调用这个方法。
参数exc_type, exc_value, traceback分别表示异常类型、异常值和调用栈。如果没有异常发生,这三个参数都是None。 如果该方法返回True,则表示异常已经被处理,不会继续向外传播;如果返回False,则异常会被重新抛出(如果没有异常,返回什么都可以)。
通常在这个方法中释放资源(如关闭文件)。
上下文管理器的种类
除了使用类定义上下文管理器,Python还提供了contextmanager装饰器(在contextlib模块中),可以快速创建基于生成器的上下文管理器。这种上下文管理器使用yield语句将函数分成两部分:yield之前的部分相当于__enter__,之后的部分相当于__exit__。
方式1:类实现(完整控制)
特点:显式定义__enter__和__exit__,拥有完整异常处理能力,支持状态管理
class FileManager:
def init(self, filename):
self.filename = filename
self.file = None
def enter(self):
print("打开文件...")
self.file = open(self.filename)
return self.file # 返回资源对象
def exit(self, exc_type, exc_val, exc_tb):
print("关闭文件...")
if self.file:
self.file.close()
处理异常(返回True则阻止异常传播)
if exc_type:
print(f"发生异常: {exc_type}")
return False # 不屏蔽异常
使用示例
with FileManager("test.txt", "w") as f:
f.write("Hello Context!") # 即使发生异常,文件也会关闭
触发异常测试: 1/0
print("操作完成")
f是返回的资源名称(可任意取),f.write是该资源自己拥有的方法。
方式2:生成器实现(简洁方案)
特点 :使用contextlib.contextmanager装饰器,用yield分隔资源获取/释放,代码更简洁
例1:
from contextlib import contextmanager
@contextmanager
def file_manager(filename, mode):
try:
print("打开文件...")
f = open(filename, mode)
yield f # 返回资源
except Exception as e:
print(f"发生异常: {type(e).name}")
finally:
print("关闭文件...")
f.close()
使用示例
with file_manager("test.txt", "a") as f:
f.write("\nAppend content")
触发异常测试: int('abc')
# 传统方式(易出错)
file = open('data.txt')
try:
data = file.read() # 若此处抛出异常,
file.close() 可能不被执行!
finally:
file.close() # 必须手动确保关闭
装饰器实现的上下文管理与普通的 try...except...finally 语句相比,核心优势在于代码复用性、可读性和扩展性,尤其更符合 "关注点分离" 的设计原则。以下从具体场景对比两者的优势:
普通 try...except 需要在每次使用资源时重复编写 "获取 - 清理" 逻辑,而装饰器实现的上下文管理可以将这部分逻辑封装成通用工具,在多个地方复用。
普通 try...except 会将 "业务逻辑" 和 "资源管理逻辑" 混在一起,而上下文管理通过 with 语句清晰分离两者,让代码意图更明确。阅读代码时,无需关注 "连接如何创建 / 关闭""异常如何处理",只需聚焦 with 块内的核心业务逻辑,降低理解成本。
普通 try...except 依赖开发者手动编写 finally 块来释放资源,容易因疏忽导致资源泄漏(如忘记写 f.close());而上下文管理通过装饰器强制封装了清理逻辑,无论开发者是否记得,资源都会被自动释放。
上下文管理通过装饰器的 finally 块(或类实现的 exit)强制执行清理操作,从机制上避免了人为遗漏,尤其适合团队协作(确保所有人都遵循资源管理规范)。
装饰器实现的上下文管理可以轻松扩展,支持嵌套、组合等复杂场景,而普通 try...except 嵌套会导致代码臃肿("金字塔问题")。
|------------|------------------|---------------------------------|
| 维度 | 装饰器上下文管理 | 普通 try...except |
| 代码复用 | 一次封装,多处复用 | 每次使用需重复编写资源管理逻辑 |
| 可读性 | 分离业务逻辑与资源管理,意图明确 | 逻辑混杂,需通读代码才能理解意图 |
| 资源安全 | 强制自动释放,避免人为失误 | 依赖手动编写 finally,易遗漏 |
| 复杂场景支持 | 简洁支持嵌套、组合等场景 | 嵌套层级深,代码臃肿 |
例2:
from contextlib import contextmanager
@contextmanagerdef session_scope():
创建新的数据库会话(相当于 enter 方法)
session = SessionLocal()
try:
将会话传递给 with 代码块使用
yield session # 暂停执行,将会话对象传递给 with 块
代码块无异常时提交事务(yield 之后的部分相当于 exit 中的正常退出逻辑)
session.commit()
except:
发生异常时回滚事务
session.rollback()
重新抛出异常
raise
finally:
始终关闭会话释放连接(相当于 exit 中的清理逻辑)
session.close()
with语句 用法
with 语句对资源的类型没有限制,只要上下文管理器的 enter 方法(或装饰器中的 yield 语句)返回的是一个合法对象,就可以作为资源变量。这个对象可以是:
基础类型(如文件对象、整数);
容器类型(如列表、字典);
可调用对象(如函数、类实例方法);
自定义类实例等。
当资源变量是函数时,它的作用通常是在 with 块内提供与当前上下文相关的操作(如示例中打印临时目录信息),同时享受上下文管理器的资源自动管理能力
with 类名(参数)/函数名(参数) as 变量:
代码块
等价于:
manager = 上下文管理器()
变量 = manager.enter()
try:
代码块
finally:
manager.exit(异常信息)
类名(参数)/函数名(参数)上面已经介绍了就是上下文管理器的2种方式,那么后面这个as 有什么作用呢?
as关键字的使用场景
|------------|------------|------------------------------|
| 情况 | 是否使用as | 说明 |
| 需要访问管理器返回值 | 必须使用 | 当 enter() 返回的值需要在代码块中使用时 |
| 仅需资源管理 | 可省略 | 不需要使用管理器返回值时 |
当需要直接操作上下文管理器返回的资源对象时:
文件操作:需要文件对象进行读写
with open('test.txt', 'w') as file: # as 接收 open() 返回的文件对象
file.write("Hello, World!") # 操作文件对象
退出 with 后文件自动关闭
当只需确保资源清理,不需要操作管理器返回值时,比如锁操作:
锁操作:只需确保锁被释放,无需直接操作锁对象
import threading
lock = threading.Lock()
with lock: # 不需要 as,因为不需要操作锁对象
print("临界区代码执行中")# 退出 with 后锁自动释放
class Timer:
def enter(self):
self.start = time.time()
def exit(self, *args):
elapsed = time.time() - self.start
print(f"代码块耗时: {elapsed:.2f}秒")
with Timer(): # 不需要 as,因为不关心返回值
time.sleep(1.5) # 模拟耗时操作
输出:
代码块耗时: 1.50秒
- 异常处理更健壮
问题解决:在 exit() 中可统一处理异常(如回滚事务、日志记录)。
原理:exit() 接收异常信息,可通过返回 True 抑制异常。
class DatabaseTransaction:
def enter(self):
self.conn = sqlite3.connect('db.db')
return self.conn.cursor()
def exit(self, exc_type, exc_val, exc_tb):
if exc_type: # 发生异常时回滚
self.conn.rollback()
else: # 否则提交
self.conn.commit()
self.conn.close()
with DatabaseTransaction() as cursor:
cursor.execute("DELETE FROM users") # 若此处失败,自动回滚