Управление менеджерами контекста в ситуациях, когда вы не хотите, чтобы оператор with
убирал вещи, если все ваши приобретения ресурсов выполнены успешно, - это один из вариантов использования, для обработки которого contextlib.ExitStack()
предназначен.
Например (с использованием addCleanup()
вместо пользовательской реализации tearDown()
):
def setUp(self):
with contextlib.ExitStack() as stack:
self._resource = stack.enter_context(GetResource())
self.addCleanup(stack.pop_all().close)
Это наиболее надежный подход, поскольку он правильно обрабатывает получение нескольких ресурсов:
def setUp(self):
with contextlib.ExitStack() as stack:
self._resource1 = stack.enter_context(GetResource())
self._resource2 = stack.enter_context(GetOtherResource())
self.addCleanup(stack.pop_all().close)
Здесь, если GetOtherResource()
завершится неудачно, первый ресурс будет немедленно очищен с помощью оператора with, а в случае успеха вызов pop_all()
отложит очистку до тех пор, пока не запустится зарегистрированная функция очистки.
Если вы знаете, что вам когда-либо понадобится управлять только одним ресурсом, вы можете пропустить оператор with:
def setUp(self):
stack = contextlib.ExitStack()
self._resource = stack.enter_context(GetResource())
self.addCleanup(stack.close)
Однако, это немного более подвержено ошибкам, так как если вы добавите большересурсов в стек без предварительного переключения на версию с оператором, успешно распределенные ресурсы не могут быть быстро очищены, если последующие ресурсыНеудачные приобретения.
Вы также можете написать что-то сопоставимое, используя пользовательскую реализацию tearDown()
, сохранив ссылку на стек ресурсов в тестовом примере:
def setUp(self):
with contextlib.ExitStack() as stack:
self._resource1 = stack.enter_context(GetResource())
self._resource2 = stack.enter_context(GetOtherResource())
self._resource_stack = stack.pop_all()
def tearDown(self):
self._resource_stack.close()
В качестве альтернативы, вы также можетеопределить пользовательскую функцию очистки, которая обращается к ресурсу через ссылку закрытия, избегая необходимости сохранять любое дополнительное состояние в тестовом примере исключительно для целей очистки:
def setUp(self):
with contextlib.ExitStack() as stack:
resource = stack.enter_context(GetResource())
def cleanup():
if necessary:
one_last_chance_to_use(resource)
stack.pop_all().close()
self.addCleanup(cleanup)