Python 中的 with 语句是上下文管理器(Context Manager)最常见、最优雅的使用方式。它主要解决资源管理的问题(文件、网络连接、数据库连接、锁、临时目录等),能保证“无论是否发生异常,资源都会被正确释放”。
with 语句的核心价值(一句话总结)
替代传统的
try-finally结构,让代码更简洁、更安全、更可读。
1. 最常见的用法 —— 文件操作
# 传统写法(容易忘记 close)
f = open('data.txt', 'r', encoding='utf-8')
try:
content = f.read()
print(content)
finally:
f.close() # 必须写在 finally 里
# 使用 with(推荐)
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)
# 这里可以随意写多行操作
# with 语句块结束后,f 自动被关闭(即使中间抛异常也一样)
print(f.closed) # True
2. with 同时打开多个资源(Python 3.1+)
with open('input.txt') as fin, open('output.txt', 'w') as fout:
for line in fin:
fout.write(line.upper())
Python 3.3+ 更推荐这种写法(括号可省略):
with (
open('input.txt', encoding='utf-8') as fin,
open('output.log', 'a', encoding='utf-8') as log
):
log.write("开始处理\n")
for line in fin:
log.write(f"处理: {line.strip()}\n")
3. 常见的内置上下文管理器
| 场景 | 写法示例 | 主要作用 |
|---|---|---|
| 文件 | with open(...) as f: | 自动关闭文件 |
| 线程锁 | with lock: | 自动获取/释放锁 |
| decimal 精度控制 | with decimal.localcontext(prec=10): | 临时改变精度,退出后恢复 |
| 临时改变目录 | with tempfile.TemporaryDirectory() as tmp: | 用完自动删除临时目录 |
| 关闭连接 | with conn:(数据库连接对象) | 自动 commit / rollback + 关闭 |
| 抑制特定异常 | with contextlib.suppress(FileNotFoundError): | 忽略指定异常,不打印 traceback |
4. 自己编写上下文管理器(两种主流方式)
方式一:基于类(最清晰、最常用)
class MyTimer:
def __init__(self, name=""):
self.name = name
def __enter__(self):
import time
self.start = time.perf_counter()
print(f"[{self.name}] 开始计时...")
return self # 可以返回 self 供 as 变量使用
def __exit__(self, exc_type, exc_value, traceback):
import time
elapsed = time.perf_counter() - self.start
print(f"[{self.name}] 结束,用时: {elapsed:.4f} 秒")
# 返回 True 表示“吞掉”异常;返回 False 或 None 则异常继续向外抛
return False
# 使用
with MyTimer("下载任务") as t:
time.sleep(1.2)
# 模拟一些工作
方式二:用 @contextmanager 装饰器(更简洁)
from contextlib import contextmanager
import time
@contextmanager
def timer(name=""):
start = time.perf_counter()
print(f"[{name}] 开始...")
try:
yield # yield 之前的代码是 __enter__,之后的代码是 __exit__
finally:
elapsed = time.perf_counter() - start
print(f"[{name}] 结束,用时: {elapsed:.3f}s")
# 使用方式和普通 with 完全一样
with timer("测试块"):
time.sleep(0.8)
print("正在干活...")
5. 常见面试/进阶问题点
| 问题 | 答案要点 |
|---|---|
| with 语句执行顺序是什么? | 1. 执行 __enter__()2. 执行 with 代码块 3. 执行 __exit__()(无论是否异常) |
__exit__ 返回 True 有什么效果? | 代表“异常已被处理”,外层不会再看到这个异常 |
| 可以用 with 写多层嵌套吗? | 可以,但建议用并列写法(Python 3.3+ 支持括号多上下文) |
| 为什么数据库连接库都支持 with? | 因为实现了上下文管理协议,能自动管理事务和连接关闭 |
contextlib.ExitStack 有什么用? | 当你需要在运行时动态决定要管理的资源数量时使用(高级用法) |
6. 总结口诀
- 凡是需要“配对使用”的资源(打开就要关闭、加锁就要解锁、进入就要退出),优先考虑用
with - 自己写上下文管理器时,优先使用
@contextmanager,代码更简洁 - 一定要记住:with 结束时一定会执行清理动作(除非进程被强杀)
希望这些例子和说明能帮你彻底搞懂 with 语句~
有哪部分还想再深入看看吗?(比如 ExitStack、多线程锁、asyncio 中的 async with 等)