Декоратор Python: сглаживание повторных координат с допустимым отклонением

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

Другие упражнения по этой теме: Декораторы Python: задачи на retry, rate limiter, кэш и Circuit Breaker.

Сглаживающий коридор видоискателя

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

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

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

Что уже дано

В редакторе представлена заготовка функции deadband_cache(width) с заглушкой NotImplementedError.

При тестировании также доступна вспомогательная функция observe_deadband_calls(width, events, responses, related=False, keyword=False), которая создаёт независимые декорированные функции, имитирует замеры и собирает статистику обращений к целевым операциям. Реализовывать её самостоятельно не нужно.

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

Реализуйте функцию-фабрику deadband_cache(width).

Она принимает параметр width — конечное неотрицательное число (int или float, от 0 до 1 000 000) — и возвращает декоратор. Этот декоратор принимает функцию func, работающую с оптикой, и возвращает функцию-обёртку.

Правила работы обёртки:

  • Целевая функция func принимает ровно одну координату x. Обёртка должна поддерживать передачу координаты как позиционным аргументом, так и по имени (x=...), и при реальном вызове func обязана передавать исходный переданный объект аргумента без подмены.
  • Координаты представляют собой конечные числа семейства int/float (включая bool) в диапазоне от -1 000 000 до 1 000 000. Никакой хэшируемости от координат или возвращаемых значений не требуется.
  • Целевая функция возвращает произвольный объект Python (включая None, ложные значения или изменяемые контейнеры). Никаких проверок на ошибки внутри func добавлять не требуется; последовательность вызовов для каждой обёртки составляет от 0 до 1000 обращений.
  • При самом первом вызове обёртки целевая функция func вызывается всегда. Полученная координата сохраняется как начальная опорная точка, а возвращённый объект — как текущий кэшированный отклик.
  • При последующих вызовах вычисляется модуль разности abs(new_coordinate - anchor): если он меньше либо равен width, обращение к func пропускается, а обёртка возвращает сохранённый кэшированный объект отклика. Попадание в коридор допуска не сдвигает опорную точку.
  • Только если abs(new_coordinate - anchor) > width, обёртка снова вызывает func с новой координатой, обновляя и опорную точку, и сохранённый объект отклика.
  • Сравнение выполняется строго по координатам, а не по значениям откликов.
  • Каждая задекорированная функция имеет полностью независимое внутреннее состояние (свою опорную координату и свой сохранённый отклик), даже если они были созданы одной и той же фабрикой deadband_cache.

Ввод и вывод

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

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

calls = []
@deadband_cache(2)
def optics(x):
    calls.append(x)
    return {'coordinate': x}
first = optics(10)
print(optics(12) is first)
print(optics(13))
print(calls)

Вывод:

True
{'coordinate': 13}
[10, 13]
РешениеPython

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

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

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