# Python上下文管理器是什么?with如何自动释放资源?
面试官问:"Python上下文管理器是什么?with语句如何自动释放资源?"
很多人会答"with可以自动关闭文件",但这只是应用场景。真正要讲清楚的是:上下文管理器是实现了__enter__()和__exit__()方法的对象,with语句在进入时调用__enter__(),在退出时无论是否异常都调用__exit__()释放资源。
# 简要回答
上下文管理器(Context Manager) 是实现了上下文管理协议的对象,需要定义两个方法:
__enter__():进入with代码块时调用,返回值赋给as后的变量。__exit__(exc_type, exc_val, exc_tb):退出with代码块时调用,无论正常退出还是异常退出都会执行。
with语句的工作流程:
- 调用上下文管理器的
__enter__()方法,返回值赋给as变量。 - 执行with代码块。
- 无论代码块是否抛出异常,都调用
__exit__()方法。 - 如果
__exit__()返回True,异常被抑制;否则异常继续传播。
为什么能自动释放资源:资源释放逻辑写在__exit__()中,Python保证无论with代码块如何退出,__exit__()都会被调用,类似其他语言的try...finally。
# 详细回答
# with语句的基本用法
最常见的场景是文件操作:
with open("data.txt") as f:
content = f.read()
print(content)
# 退出with块后,文件自动关闭
2
3
4
等价于:
f = open("data.txt")
try:
content = f.read()
print(content)
finally:
f.close()
2
3
4
5
6
with语句简化了try...finally的写法,保证资源一定会被释放。
# 实现自定义上下文管理器
通过类实现上下文管理协议:
class DatabaseConnection:
def __init__(self, host):
self.host = host
self.conn = None
def __enter__(self):
print(f"连接数据库 {self.host}")
self.conn = f"Connection to {self.host}"
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"关闭数据库连接")
self.conn = None
return False # 不抑制异常
with DatabaseConnection("localhost") as conn:
print(f"使用连接: {conn}")
print("执行查询")
# 输出:
# 连接数据库 localhost
# 使用连接: Connection to localhost
# 执行查询
# 关闭数据库连接
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__enter__()返回的值赋给conn,__exit__()在退出时自动调用。
# __exit__的异常处理
__exit__()接收三个参数,描述with代码块中的异常:
exc_type:异常类型。exc_val:异常实例。exc_tb:异常traceback。
如果没有异常,三个参数都是None。
class Resource:
def __enter__(self):
print("获取资源")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
print("正常退出,释放资源")
else:
print(f"异常退出: {exc_type.__name__}, 释放资源")
return False
with Resource():
print("使用资源")
raise ValueError("出错了")
# 输出:
# 获取资源
# 使用资源
# 异常退出: ValueError, 释放资源
# (然后抛出ValueError)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__exit__()返回False或不返回值(默认None)时,异常继续传播。返回True时,异常被抑制。

# 抑制异常
__exit__()返回True可以吞掉异常:
class SuppressException:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ValueError:
print("捕获ValueError,不传播")
return True # 抑制异常
return False
with SuppressException():
print("开始")
raise ValueError("错误")
print("这行不会执行")
print("继续执行")
# 输出:
# 开始
# 捕获ValueError,不传播
# 继续执行
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这种方式不推荐,因为隐式吞掉异常会让调试困难。通常应该让异常传播,在外层统一处理。
# 使用contextlib简化实现
contextlib.contextmanager装饰器可以用生成器实现上下文管理器:
from contextlib import contextmanager
@contextmanager
def database_connection(host):
print(f"连接数据库 {host}")
conn = f"Connection to {host}"
try:
yield conn # yield之前是__enter__,之后是__exit__
finally:
print("关闭数据库连接")
with database_connection("localhost") as conn:
print(f"使用连接: {conn}")
2
3
4
5
6
7
8
9
10
11
12
13
yield之前的代码相当于__enter__(),yield的值赋给as变量,yield之后的代码相当于__exit__()。try...finally保证清理代码一定执行。
# 多个上下文管理器
可以在一个with语句中使用多个上下文管理器:
with open("input.txt") as f_in, open("output.txt", "w") as f_out:
content = f_in.read()
f_out.write(content.upper())
2
3
等价于嵌套with:
with open("input.txt") as f_in:
with open("output.txt", "w") as f_out:
content = f_in.read()
f_out.write(content.upper())
2
3
4
多个上下文管理器从左到右依次进入,从右到左依次退出。
# 知识拓展
Q1: 为什么文件对象可以用with语句?
文件对象实现了上下文管理协议。open()返回的文件对象有__enter__()返回自己,__exit__()调用close()方法:
f = open("data.txt")
print(hasattr(f, "__enter__")) # True
print(hasattr(f, "__exit__")) # True
2
3
Q2: 上下文管理器的典型应用场景有哪些?
- 文件操作:自动关闭文件。
- 数据库连接:自动关闭连接或提交/回滚事务。
- 锁管理:
threading.Lock自动释放锁。 - 临时修改状态:临时改变工作目录、环境变量、日志级别。
- 性能计时:记录代码块执行时间。
Q3: 如果__enter__抛出异常,__exit__还会执行吗?
不会。只有成功进入with代码块(即__enter__()成功返回),__exit__()才会执行。__enter__()抛出异常时,上下文管理器没有被激活,不需要清理。
Q4: contextlib还有哪些有用的工具?
contextlib.closing:为没有实现上下文管理协议但有close()方法的对象提供with支持。contextlib.suppress:简洁地抑制指定异常类型。contextlib.ExitStack:动态管理多个上下文管理器。
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("nonexistent.txt")
# 文件不存在时不报错,继续执行
2
3
4
5
Q5: with语句可以用于异步场景吗?
可以。Python 3.5+支持异步上下文管理器,需要实现__aenter__()和__aexit__():
class AsyncResource:
async def __aenter__(self):
print("异步获取资源")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("异步释放资源")
return False
async def main():
async with AsyncResource():
print("使用资源")
2
3
4
5
6
7
8
9
10
11
12
配合async with使用,适用于异步数据库连接、异步文件操作等场景。
评论
验证登录状态...