跳到主要内容

上下文管理器

问题

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_typeNone异常类型(如 ValueError
exc_valNone异常实例
exc_tbNonetraceback 对象

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())

相关链接