上下文管理器是Python中用于管理资源的对象,它定义了在进入和退出代码块时要执行的操作。最常见的用法是通过with语句来管理文件操作、数据库连接等需要明确释放的资源。
在底层实现上,上下文管理器必须实现__enter__和__exit__两个魔法方法。当执行with语句时:
__enter__方法,返回值会赋给as后的变量with块内的代码__exit__方法python复制class MyContextManager:
def __enter__(self):
print("进入上下文")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("退出上下文")
if exc_type is not None:
print(f"发生异常: {exc_val}")
return True # 如果返回True,则抑制异常
# 使用示例
with MyContextManager() as cm:
print("正在执行代码块")
# raise ValueError("测试异常") # 取消注释测试异常处理
在没有上下文管理器之前,资源管理通常采用try-finally模式:
python复制file = open('example.txt', 'w')
try:
file.write('Hello World')
finally:
file.close()
这种模式存在几个问题:
close()方法上下文管理器的优势在于:
提示:Python内置的
open()函数返回的文件对象本身就是一个上下文管理器,这也是为什么我们可以直接使用with open() as f语法。
对于复杂的资源管理场景,我们可以通过类来实现自定义上下文管理器。下面是一个数据库连接的上下文管理器示例:
python复制import sqlite3
class DatabaseConnection:
def __init__(self, db_name):
self.db_name = db_name
self.conn = None
def __enter__(self):
self.conn = sqlite3.connect(self.db_name)
print(f"已连接到数据库 {self.db_name}")
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
if self.conn:
if exc_type is None:
self.conn.commit()
print("事务已提交")
else:
self.conn.rollback()
print(f"发生异常,已回滚: {exc_val}")
self.conn.close()
print("连接已关闭")
return True
# 使用示例
with DatabaseConnection('example.db') as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
cursor.execute("INSERT INTO users (name) VALUES ('Alice')")
# raise ValueError("测试异常") # 取消注释测试异常情况
对于简单的场景,Python标准库中的contextlib模块提供了更简洁的实现方式。最常用的是@contextmanager装饰器:
python复制from contextlib import contextmanager
import time
@contextmanager
def timer(name):
start = time.time()
try:
yield
finally:
duration = time.time() - start
print(f"{name} 耗时: {duration:.2f}秒")
# 使用示例
with timer("数据处理"):
# 模拟耗时操作
time.sleep(1.5)
这种实现方式的要点:
yield语句yield前的代码相当于__enter__yield后的代码相当于__exit__yield返回的值会赋给as后的变量Python支持同时使用多个上下文管理器,有两种写法:
python复制# 方式一:嵌套写法
with open('input.txt') as f_in:
with open('output.txt', 'w') as f_out:
f_out.write(f_in.read())
# 方式二:单行写法(推荐)
with open('input.txt') as f_in, open('output.txt', 'w') as f_out:
f_out.write(f_in.read())
嵌套上下文管理器的一个重要特性是:如果内层管理器抛出异常,外层管理器仍然能正常执行__exit__方法。
某些场景下,我们可能需要同一个上下文管理器多次进入和退出。下面是一个线程锁的可重入实现:
python复制from threading import Lock
from contextlib import ContextDecorator
class ReentrantLock(ContextDecorator):
def __init__(self):
self._lock = Lock()
self._owner = None
self._count = 0
def __enter__(self):
current_thread = threading.current_thread()
if self._owner != current_thread:
self._lock.acquire()
self._owner = current_thread
self._count += 1
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._count -= 1
if self._count == 0:
self._owner = None
self._lock.release()
# 作为装饰器使用
def __call__(self, func):
def wrapped(*args, **kwargs):
with self:
return func(*args, **kwargs)
return wrapped
# 使用方式一:with语句
lock = ReentrantLock()
with lock:
# 临界区代码
pass
# 使用方式二:装饰器
@lock
def critical_function():
# 临界区代码
pass
Python 3.5+引入了async with语法支持异步上下文管理器,需要实现__aenter__和__aexit__方法:
python复制import aiohttp
class AsyncWebPageDownloader:
def __init__(self, url):
self.url = url
async def __aenter__(self):
self.session = aiohttp.ClientSession()
async with self.session.get(self.url) as response:
self.content = await response.text()
return self.content
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.session.close()
# 使用示例
async def main():
async with AsyncWebPageDownloader('https://example.com') as content:
print(content[:100]) # 打印网页前100个字符
在SQLAlchemy中,上下文管理器常用于管理数据库会话和事务:
python复制from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from contextlib import contextmanager
engine = create_engine('sqlite:///example.db')
Session = sessionmaker(bind=engine)
@contextmanager
def session_scope():
"""提供事务范围的会话上下文管理器"""
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
# 使用示例
with session_scope() as session:
# 执行数据库操作
user = User(name='Bob', email='bob@example.com')
session.add(user)
# 不需要显式调用commit(),上下文管理器会自动处理
处理临时文件时,确保临时目录被正确清理非常重要:
python复制import tempfile
import shutil
from contextlib import contextmanager
@contextmanager
def temp_directory():
"""创建并自动清理临时目录的上下文管理器"""
temp_dir = tempfile.mkdtemp()
try:
yield temp_dir
finally:
shutil.rmtree(temp_dir)
# 使用示例
with temp_directory() as temp_dir:
print(f"临时目录: {temp_dir}")
# 在此处处理临时文件
# 退出with块后,目录会被自动删除
开发过程中,我们经常需要测量代码块的执行时间:
python复制import time
from contextlib import contextmanager
@contextmanager
def profile(name='代码块', print_result=True):
"""性能分析上下文管理器"""
start = time.perf_counter()
try:
yield
finally:
elapsed = time.perf_counter() - start
if print_result:
print(f"{name} 耗时: {elapsed:.6f}秒")
# 使用示例
with profile("复杂计算"):
# 执行需要测量的代码
result = sum(i*i for i in range(10**6))
常见错误是在__exit__中没有正确处理异常,导致资源泄漏:
python复制# 错误示例
class BadContextManager:
def __enter__(self):
print("进入")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("退出")
# 没有返回True或False,异常会继续传播
# 正确做法应返回True来抑制异常,或False/None让异常传播
解决方案:
__exit__中检查exc_type是否为None来判断是否有异常发生return True)还是传播异常初学者容易混淆@contextmanager装饰器和普通生成器:
python复制# 错误理解
@contextmanager
def generator_like(): # 这不是一个普通生成器
print("开始")
yield "值"
print("结束")
# 正确理解:这是一个上下文管理器工厂函数
关键区别:
yield后的代码一定会执行yield后的代码不会执行理解嵌套上下文管理器的执行顺序很重要:
python复制@contextmanager
def cm(name):
print(f"{name} 进入")
try:
yield
finally:
print(f"{name} 退出")
with cm("外层"), cm("内层"):
print("执行代码块")
# 输出顺序:
# 外层 进入
# 内层 进入
# 执行代码块
# 内层 退出
# 外层 退出
记住规则:
__exit__执行@contextmanager代替类:对于简单场景,函数式实现更轻量编写单元测试验证上下文管理器的行为:
python复制import unittest
from unittest.mock import MagicMock
class TestContextManager(unittest.TestCase):
def test_normal_execution(self):
mock_enter = MagicMock()
mock_exit = MagicMock(return_value=None)
class MockCM:
def __enter__(self):
return mock_enter()
def __exit__(self, *args):
return mock_exit(*args)
with MockCM() as cm:
pass
mock_enter.assert_called_once()
mock_exit.assert_called_once_with(None, None, None)
def test_exception_handling(self):
mock_exit = MagicMock(return_value=True) # 抑制异常
class MockCM:
def __enter__(self):
return self
def __exit__(self, *args):
return mock_exit(*args)
try:
with MockCM():
raise ValueError("测试异常")
except ValueError:
self.fail("异常未被抑制")
args = mock_exit.call_args[0]
self.assertEqual(args[0], ValueError)
self.assertEqual(str(args[1]), "测试异常")
虽然Python的with语句是上下文管理器的典型实现,但这种设计模式在其他语言中也有类似实现:
Java 7+引入了类似的语法:
java复制// Java示例
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} // 资源会自动关闭
C#使用using关键字实现类似功能:
csharp复制// C#示例
using (var file = new StreamWriter("file.txt"))
{
file.WriteLine("Hello World");
} // 文件会自动关闭
C++通过构造函数和析构函数实现资源获取即初始化(RAII):
cpp复制// C++示例
{
std::ofstream file("example.txt");
file << "Hello World";
// 文件会在离开作用域时自动关闭
}
这些实现虽然语法不同,但核心思想一致:通过语言特性确保资源被正确释放,减少手动管理带来的错误。