Контекстный менеджер Python: откат списания ресурсов при ошибке

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

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

Реестр снаряжения

База проката оформляет комплексную выдачу экипировки для туристической группы. Инвентарь списывается из центрального реестра поштучно прямо во время подбора снаряжения. Если в процессе комплектации нужной вещи не оказывается на складе или клиент внезапно отказывается от поездки, оформление срывается исключением.

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

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

Что уже дано

В заготовке объявлен класс EquipmentLedger с заготовками методов __init__, __enter__, take и __exit__, которые вам предстоит реализовать.

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

Реализуйте класс EquipmentLedger(stock: dict[str, int]) со следующим поведением:

  • Конструктор принимает изменяемый словарь stock, где ключи — названия предметов снаряжения (строки длиной от 1 до 100 символов, всего от 0 до 200 ключей), а значения — их неотрицательные целочисленные остатки на складе (от 0 до 1 000 000). Отсутствие ключа означает, что доступных единиц предмета нет (0).
  • Метод __enter__() активирует контекст и возвращает сам экземпляр менеджера (self).
  • Метод take(item: str) выполняет поштучное списание (в рамках контекста выполняется от 0 до 1000 операций):
  • если остаток item в переданном словаре stock строго больше нуля, метод немедленно уменьшает этот остаток ровно на 1 и записывает списание на счёт текущего менеджера;
  • если предмета нет в словаре или его остаток равен нулю, метод возбуждает исключение LookupError(item), ничего не списывая и не фиксируя.
  • При нормальном завершении блока with все выполненные списания остаются в силе.
  • Если внутри блока with возникает любое исключение (включая сбой самого метода take или внешние ошибки), метод __exit__(exc_type, exc_val, exc_tb) возвращает на склад ровно те единицы, которые были успешно списаны этим экземпляром менеджера, после чего исключение обязано беспрепятственно всплыть дальше (ошибки не подавляются).
  • Откат восстанавливает только «свои» списания: любые независимые изменения словаря stock, сделанные другим кодом или другим менеджером во время работы блока (как по затронутым, так и по посторонним ключам), должны сохраниться.
  • Никаких отложенных копий при успехе: списания происходят по месту прямо во время вызова take.

Ввод и вывод

Стандартный поток ввода stdin не используется. Печатать через print в stdout ничего не нужно: после выполнения вашего кода тестирующая система считывает из глобального пространства имён класс верхнего уровня EquipmentLedger, создаёт экземпляры менеджера вокруг общих словарей остатков и проверяет их поведение внутри и после блоков with.

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

stock = {'tent': 2, 'lamp': 3}
try:
    with EquipmentLedger(stock) as rental:
        rental.take('tent')
        stock['lamp'] += 1
        raise RuntimeError('cancelled')
except RuntimeError:
    pass
print(stock)

Вывод:

{'tent': 2, 'lamp': 4}
РешениеPython

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

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

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