Дескрипторы Python: валидация и сброс кэша зависимых свойств

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

Другие упражнения по этой теме: Дескрипторы Python: задачи на get, set и set_name.

Инвалидация кэша в дескрипторе координат

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

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

Реализуйте data-дескриптор первичного поля, который при каждом успешном присваивании инвалидирует список связанных ленивых свойств в словаре экземпляра.

Что уже дано

В среде выполнения уже объявлены вспомогательные инструменты для тестирования: non-data дескриптор LazyValue, фабрика валидатора make_cache_probe() и фабрика тестового класса make_cache_board(). Заново реализовывать их не нужно.

Вам предстоит заполнить класс CacheInvalidatingField методами __init__, __set_name__, __get__ и __set__.

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

Реализуйте data-дескриптор CacheInvalidatingField(validator, dependent_names) со следующими методами:

  • __init__(self, validator, dependent_names):
  • validator — вызываемый объект для проверки новых значений;
  • dependent_names — последовательность строковых имён зависимых атрибутов, кэш которых нужно сбрасывать при обновлении поля (от 0 до 16 имён, возможны дубликаты).
  • __set_name__(self, owner, name):
  • запоминает имя поля name, связывая дескриптор с классом-владельцем.
  • __set__(self, instance, value):
  • сначала однократно вызывает validator(value). Если валидатор возбуждает исключение, дескриптор не перехватывает его, оставляет значение поля и кэши в instance.__dict__ нетронутыми и прекращает операцию;
  • при успешной валидации (нормальный возврат из validator) сохраняет исходное переданное значение value в словаре текущего экземпляра instance.__dict__ под закрытым ключом, состоящим из символа подчёркивания и имени поля: f"_{name}";
  • удаляет из словаря instance.__dict__ все зарегистрированные зависимые имена dependent_names. Если какого-то имени в словаре объекта нет, это не считается ошибкой. Не зарегистрированные зависимые имена удаляться не должны;
  • изменения применяются строго к целевому объекту instance и не затрагивают другие экземпляры. Принудительный расчёт зависимых свойств при присваивании не выполняется.
  • __get__(self, instance, owner=None):
  • при обращении через класс (instance is None) возвращает сам объект дескриптора (self);
  • при обращении через экземпляр возвращает ранее сохранённое значение по закрытому ключу f"_{name}";
  • если полю в этом экземпляре значение ещё ни разу успешно не присваивалось, возбуждает AttributeError.

Ввод и вывод

Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля класс верхнего уровня CacheInvalidatingField и создаёт класс-владелец с дескриптором в его пространстве имён, благодаря чему Python автоматически вызывает __set_name__.

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

probe = make_cache_probe()
field = CacheInvalidatingField(probe.validate, ("length", "duration"))
board = make_cache_board(field, "points", ("length", "duration"), 1)
route = board.instances[0]
route.points = [1, 2]
_ = route.length
print("length" in route.__dict__)
route.points = [9]
print("length" in route.__dict__)
_ = route.length
print(board.calls[0]["length"])

Вывод:

True
False
2
РешениеPython

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

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

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