上下文管理器
问题
Python 的 with 语句原理是什么?如何自定义上下文管理器?contextlib 有哪些常用工具?
答案
with 语句与上下文管理器协议
上下文管理器需要实现 __enter__ 和 __exit__ 两个方法:
class FileManager:
def __init__(self, path: str, mode: str = "r"):
self.path = path
self.mode = mode
self.file = None
def __enter__(self):
"""进入 with 块时调用,返回值绑定到 as 变量"""
self.file = open(self.path, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""退出 with 块时调用(无论是否异常)"""
if self.file:
self.file.close()
# 返回 True 表示吞掉异常;返回 False/None 表示继续传播异常
return False
with FileManager("test.txt", "w") as f:
f.write("Hello")
# 退出 with 块后,文件自动关闭
__exit__ 参数说明:
| 参数 | 正常退出 | 异常退出 |
|---|---|---|
exc_type | None | 异常类型(如 ValueError) |
exc_val | None | 异常实例 |
exc_tb | None | traceback 对象 |
contextlib 工具
@contextmanager — 用生成器实现上下文管理器(最常用):
from contextlib import contextmanager
import time
@contextmanager
def timer(name: str):
"""计时上下文管理器"""
start = time.perf_counter()
try:
yield # yield 之前 = __enter__,yield 之后 = __exit__
finally:
elapsed = time.perf_counter() - start
print(f"{name} 耗时 {elapsed:.4f}s")
with timer("数据处理"):
time.sleep(1)
# 数据处理 耗时 1.0008s
suppress — 忽略指定异常:
from contextlib import suppress
import os
# 等价于 try/except pass
with suppress(FileNotFoundError):
os.remove("nonexistent.txt")
redirect_stdout — 重定向输出:
from contextlib import redirect_stdout
import io
f = io.StringIO()
with redirect_stdout(f):
print("捕获这段输出")
output = f.getvalue() # "捕获这段输出\n"
异步上下文管理器
import aiohttp
class AsyncHTTPClient:
async def __aenter__(self):
self.session = aiohttp.ClientSession()
return self.session
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.session.close()
async def fetch():
async with AsyncHTTPClient() as session:
async with session.get("https://example.com") as resp:
return await resp.text()
常见面试问题
Q1: 为什么要用 with 语句而不是 try/finally?
答案:
with 语句更简洁,且将资源管理逻辑封装复用:
# try/finally — 重复代码多
f = open("file.txt")
try:
data = f.read()
finally:
f.close()
# with — 简洁且可复用
with open("file.txt") as f:
data = f.read()
Q2: __exit__ 返回 True 和 False 有什么区别?
答案:
- 返回
True:吞掉异常,with 块外不会看到异常 - 返回
False/None:异常继续向外传播
class SuppressError:
def __enter__(self): return self
def __exit__(self, *args):
return True # 吞掉所有异常
with SuppressError():
raise ValueError("这个异常不会传播")
print("继续执行") # ✅ 正常到达
Q3: 一个 with 语句可以管理多个资源吗?
答案:
# Python 3.1+:逗号分隔
with open("input.txt") as fin, open("output.txt", "w") as fout:
fout.write(fin.read())
# Python 3.10+:括号换行
with (
open("input.txt") as fin,
open("output.txt", "w") as fout,
):
fout.write(fin.read())