Сообщить об ошибке.

Менеджер контекста вместо try-finally

Шаблон, который иногда можно видеть - представляет собой оператор try ... finally с переменной flag, указывающей, следует ли выполнять тело в предложении finally. В своей простейшей форме, она выглядит примерно так:

cleanup_needed = True
try:
    result = perform_operation()
    if result:
        cleanup_needed = False
finally:
    if cleanup_needed:
        cleanup_resources()

Как и в случае любого кода на основе операторов try, это может вызвать проблемы при разработке и проверке, поскольку код установки result и код очистки cleanup_resources() могут в конечном итоге разделяться произвольно длинными разделами кода.

Класс contextlib.ExitStack() позволяет вместо этого зарегистрировать обратный вызов для выполнения в конце оператора with, а затем позднее принять решение, что с этим обратным вызовом делать:

from contextlib import ExitStack

with ExitStack() as stack:
    stack.callback(cleanup_resources)
    result = perform_operation()
    if result:
        stack.pop_all()

Код примера позволяет заданному поведению очистки быть явным заранее, вместо того, чтобы прибегать к переменной флага cleanup_needed.

Если приложение часто использует этот шаблон, то его можно еще больше упростить с помощью небольшого вспомогательного класса:

from contextlib import ExitStack

class Callback(ExitStack):
    def __init__(self, callback, /, *args, **kwds):
        super(Callback, self).__init__()
        self.callback(callback, *args, **kwds)

    def cancel(self):
        self.pop_all()

# использование
with Callback(cleanup_resources) as cb:
    result = perform_operation()
    if result:
        cb.cancel()

Если очистка ресурса к тому же объявлена как отдельная функция, то можно использовать форму декоратора метода ExitStack.callback(), чтобы объявить очистку ресурса заранее:

from contextlib import ExitStack

with ExitStack() as stack:
    @stack.callback
    def cleanup_resources():
        ...
    result = perform_operation()
    if result:
        stack.pop_all()

Благодаря тому, что протокол декоратора работает, объявленная таким образом функция обратного вызова не может принимать никаких параметров. По этому все освобождаемые ресурсы должны быть доступны как переменные для закрытия.