У меня есть некоторые ресурсы, которые я обернул в класс менеджера контекста.
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
и сделать это самменеджер контекста.
Это моя первая попытка создать класс Task
, который управляет 2 ресурсами:
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
вызывал его для меня неявно.Есть ли способ использовать менеджер контекста внутри класса, как я пытаюсь сделать?
Как правильно обрабатывать несколько ресурсов в одном диспетчере контекста?