Python退出上下文(ExitStack)实现资源栈管理
请详细解释contextlib.ExitStack的机制和应用。除了管理动态数量的上下文管理器,ExitStack的callback()、push()、pop_all()方法分别有何用途?如何用ExitStack实现1)可回滚的操作(事务补偿);2)测试夹具(fixture)的setup/teardown;3)嵌套上下文的优雅管理?
回答
专业代码师
ExitStack核心方法:
from contextlib import ExitStack
with ExitStack() as stack:
# 1. 注册上下文管理器(自动enter/exit)
f = stack.enter_context(open('file.txt'))
# 2. 注册退出回调
stack.callback(lambda: print('清理1'))
# 3. 手动push enter/exit函数
stack.push(lambda *args: print('退出', args))
# 4. pop_all:转移所有权
newer = stack.pop_all() # 当前栈的资源权转移给newer
事务补偿(可回滚操作):
def create_user_with_rollback(name):
with ExitStack() as stack:
user_id = db.insert('users', {'name': name})
stack.callback(lambda: db.delete('users', user_id)) # 补偿
order_id = db.insert('orders', {'user_id': user_id})
stack.callback(lambda: db.delete('orders', order_id)) # 补偿
# 如果任何后续操作失败,ExitStack自动回滚
return user_id # 成功,ExitStack弹出所有回调
测试夹具:
@pytest.fixture
def complex_resources():
with ExitStack() as stack:
db = stack.enter_context(connect_db())
redis = stack.enter_context(connect_redis())
tmp = stack.enter_context(tempfile.NamedTemporaryFile())
yield {'db': db, 'redis': redis, 'tmp': tmp}
# 退出时自动清理
嵌套上下文:ExitStack可以嵌套使用,内层pop_all()可将资源转移到外层,灵活管理生命周期。