AI大模型入门-Python进阶-上下文管理与with语句
视频链接:
不如语冰
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__。这两个方法定义在上下文管理器对象中。
1. __enter__(self):
当进入with语句块时,会调用这个方法。该方法返回的资源(是类吗?)会被赋值给as后面的变量(如果有的话)。 通常在这个方法中获取资源(如打开文件)。
2. __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秒
2. 异常处理更健壮
问题解决:在 __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") # 若此处失败,自动回滚
更多推荐




所有评论(0)