我有一些包装在上下文管理器类中的资源。
class Resource:
def __init__(self, res):
print(f'allocating resource {res}')
self.res = res
def __enter__(self):
return self.res
def __exit__(self, typ, value, traceback):
print(f'freed resource {self.res}')
def __str__(self):
return f'{self.res}'
如果我直接使用2种资源,则可以使用以下语法:
with Resource('foo') as a, Resource('bar') as b:
print(f'doing something with resource a({a})')
print(f'doing something with resource b({b})')
这按预期工作:
allocating resource foo allocating resource bar doing something with resource a(foo) doing something with resource b(bar) freed resource bar freed resource foo
但是,我想做的就是将这些多种资源的使用包装到一个类Task
中,并使其本身成为上下文管理器。
这是我第一次尝试创建管理2种资源的Task
类:
class Task:
def __init__(self, res1, res2):
self.a = Resource(res1)
self.b = Resource(res2)
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.b.__exit__(type, value, traceback)
self.a.__exit__(type, value, traceback)
def run(self):
print(f'running task with resource {self.a} and {self.b}')
我现在可以使用熟悉的语法:
with Task('foo', 'bar') as t:
t.run()
这又按预期工作:
allocating resource foo allocating resource bar running task with resource foo and bar freed resource bar freed resource foo
这一切都很好,除非试图释放其中一个资源时引发异常。
为了说明我已经修改了Resource
类,以为其中一种资源引发异常:
class Resource:
def __init__(self, res):
print(f'allocating resource {res}')
self.res = res
def __enter__(self):
return self.res
def __exit__(self, typ, value, traceback):
print(f'try free resource {self.res}')
if self.res == 'bar':
raise RuntimeError(f'error freeing {self.res} resource')
print(f'freed resource {self.res}')
def __str__(self):
return f'{self.res}'
先前使用2种资源的情况是
try:
with Resource('foo') as a, Resource('bar') as b:
print(f'doing something with resource a({a})')
print(f'doing something with resource b({b})')
except:
pass
面对异常释放bar
,foo
仍然被释放:
allocating resource foo
allocating resource bar
doing something with resource a(foo)
doing something with resource b(bar)
try free resource bar
try free resource foo
freed resource foo
但是,与Task
相同,我泄漏了第二个资源:
try:
with Task('foo', 'bar') as t:
t.run()
except:
pass
输出显示我从未尝试免费foo
:
allocating resource foo allocating resource bar running task with resource foo and bar try free resource bar
问题:
我可以将对Resource.__exit__
的每个显式调用都包装在try
/ except
块中,但是如果我想将异常进一步传播到调用堆栈中,同时仍然释放占用所有资源,我必须跟踪所有异常,然后再将其抛出...这感觉不正确。
从Resource.__exit__
显式地调用Task.__exit__
而不是让with
语句为我隐式调用它,也感到“肮脏”。有没有办法在我想要的类中使用上下文管理器 ?
在单个上下文管理器中处理多个资源的正确方法是什么?
答案 0 :(得分:1)
如评论中所述,ExitStack
正是这样做的。
旨在简化编程方式组合其他上下文管理器的上下文管理器
您可以简单地从ExitStack
继承并为要管理的每个资源调用enter_context
:
class Task(contextlib.ExitStack):
def __init__(self, res1, res2):
super().__init__()
self.a = self.enter_context(Resource(res1))
self.b = self.enter_context(Resource(res2))
def run(self):
print(f'running task with resource {self.a} and {self.b}')
请注意,无需像__enter__
那样定义您自己的__exit__
和ExitStack
函数。
在示例中使用它:
try:
with Task('foo', 'bar') as t:
t.run()
except:
pass
现在,当引发异常以释放bar
时,foo
仍被释放:
allocating resource foo allocating resource bar running task with resource foo and bar try free resource bar try free resource foo freed resource foo