Контекстный менеджер Python: вложенные транзакции и глубина входа

Напишите решение на Python и проверьте его на странице.

Другие упражнения по этой теме: Контекстные менеджеры Python: задачи на with, откат и исключения.

Менеджер внешних транзакций

Модуль пакетной обработки выполняет иерархические операции над единым реестром задач. Разные функции могут вызывать друг друга и вложенно открывать сессии синхронизации через один и тот же менеджер контекста.

Чтобы не создавать лишних накладных расходов, система отслеживает общую глубину вложенности. При самом первом входе в блок контекстный менеджер фиксирует контрольную точку и выставляет флаг монопольного владения. Если внутри уже открытой сессии происходит повторный вход, повторный захват ресурса не выполняется: менеджер лишь увеличивает счётчик активных контекстов.

При выходе из блоков счётчик уменьшается, но фактическая финализация реестра откладывается до выхода из самого внешнего контекста, когда глубина обращается в ноль. Любая ошибка на любом уровне вложенности переводит сессию в аварийный режим, и при итоговом завершении внешнего блока вся цепочка операций должна быть полностью отменена.

Спроектируйте менеджер контекста для управления глубиной вложенности, который изолирует промежуточные вызовы и выполняет фиксацию либо откат изменений строго при закрытии самого верхнего блока.

Что уже дано

В среде выполнения доступен класс реестра задач и вспомогательные фабрики, включая make_registry(initial). У передаваемого ресурса есть:

  • data — рабочий изменяемый словарь с данными реестра (строковые ключи, целочисленные значения);
  • active — булев флаг активности транзакции (изначально False);
  • events — список зафиксированных событий (изначально пуст);
  • метод begin() — сохраняет снимок словаря data, устанавливает active = True и добавляет 'begin' в events;
  • метод commit() — сохраняет текущие изменения словаря data, сбрасывает active в False и добавляет 'commit' в events;
  • метод rollback() — восстанавливает словарь data из сохранённого снимка, сбрасывает active в False и добавляет 'rollback' в events.

Вам предстоит реализовать класс OutermostTransaction.

Что нужно сделать

Реализуйте класс OutermostTransaction(resource) с поддержкой протокола контекстного менеджера (__enter__ и __exit__).

Класс должен соблюдать следующие правила:

  • Конструктор принимает объект реестра resource.
  • При самом первом входе в контекст (на внешнем уровне) вызывается resource.begin(). При любых вложенных входах в тот же экземпляр менеджера повторный вызов begin() не выполняется — увеличивается лишь текущая глубина вложенности.
  • Метод __enter__() на любом уровне вложенности возвращает именно оригинальный словарь resource.data.
  • Во время работы всех вложенных блоков сессия остаётся активной. На промежуточных выходах из вложенных контекстов методы commit() и rollback() не вызываются.
  • Если в метод __exit__ на любой глубине передаётся информация об исключении (exc_type is not None), сессия безвозвратно помечается как аварийная. Даже если вызывающий код перехватил эту ошибку снаружи внутреннего блока и продолжил выполнение внешнего блока, статус аварийного сбоя для всей сессии сохраняется.
  • Исключения никогда не подавляются: метод __exit__ должен позволять им всплывать дальше (возвращая False или None), сохраняя исходный объект ошибки.
  • Фактическая финализация происходит строго при выходе из самого внешнего блока (когда глубина вложенности возвращается к 0):
  • если за время всей сессии возникала хотя бы одна ошибка, вызывается resource.rollback();
  • если ни одной ошибки не было, вызывается resource.commit().

Параметры входных данных:

  • Число ключей в реестре — от 0 до 30, длина строковых ключей — от 1 до 64 символов, значения — целые числа от -1 000 000 до 1 000 000.
  • Глубина вложенности — от 1 до 50 уровней, количество операций в теле блоков — от 0 до 1000.

Ввод и вывод

Стандартные потоки stdin и stdout не используются: считывать данные через input() и печатать результат через print() не нужно. Проверяющая система после выполнения кода ученика получает из пространства имён объявленный на верхнем уровне класс OutermostTransaction и тестирует его работу серией вложенных вызовов.

Пример использования

resource = make_registry({"count": 1})
session = OutermostTransaction(resource)
with session as data:
    data["count"] = 2
    try:
        with session as inner:
            inner["extra"] = 3
            raise ValueError("cancel")
    except ValueError:
        pass
print(resource.data)
print(resource.events)
print(resource.active)

Вывод:

{'count': 1}
['begin', 'rollback']
False
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи