Декоратор Python: валидация направления и размера перемещения

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

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

Ограничитель шага каретки

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

За перемещениями следит параметризованный декоратор, который настраивается на допустимое направление движения (только вперёд или только назад) и максимальный шаг за один вызов. В замыкании декоратор хранит текущую абсолютную координату каретки, которая изначально равна нулю. Если полученная абсолютная координата совпадает с текущей, нарушает выбранный знак строгого ненулевого приращения или модуль смещения оказывается строго больше допустимого максимума, декоратор не вызывает двигатель и сразу возбуждает ValueError, сохраняя прежнюю координату.

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

Реализуйте этот декоратор валидации, обеспечивающий безопасное и монотонное позиционирование каретки.

Что уже дано

В редакторе представлена заготовка функции-фабрики directional_steps(direction, max_step). Вспомогательная проверочная функция observe_directional_steps уже доступна в тестовом окружении, реализовывать её не требуется.

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

Реализуйте фабрику декоратора directional_steps(direction, max_step).

Функция принимает два параметра:

  • direction (int) — разрешённое направление движения: +1 (только вперёд) или -1 (только назад);
  • max_step (int или float) — конечное неотрицательное число в диапазоне от 0 до 1 000 000, задающее максимальную абсолютную величину смещения за один вызов.

Фабрика возвращает декоратор, принимающий целевую функцию двигателя motor. Декоратор возвращает функцию-обёртку, которая принимает абсолютную координату x (число в диапазоне от -1 000 000 до 1 000 000), а затем произвольные позиционные и именованные аргументы исходной функции (*args, **kwargs).

Правила проверки и фиксации перемещений:

  • Для каждого задекорированного двигателя отсчёт начинается с координаты 0. Состояния разных двигателей независимы, даже если они созданы одной фабрикой.
  • Смещение вычисляется как разность между переданной координатой x и последней успешно зафиксированной позицией: displacement = x - current_pos.
  • Перемещение отклоняется возбуждением ValueError (или его подкласса) до вызова функции двигателя, если:
  • смещение равно нулю (displacement == 0);
  • знак смещения не соответствует строго направлению (при direction == 1 требуется displacement > 0, а при direction == -1 требуется displacement < 0);
  • абсолютная величина смещения строго больше допустимой (abs(displacement) > max_step).
  • Ненулевое смещение, модуль которого в точности равен max_step, считается допустимым.
  • При отклонении перемещения сохранённая координата не изменяется.
  • Если шаг допустим, декоратор вызывает целевую функцию двигателя ровно один раз, передавая ей исходную координату x со всеми переданными аргументами вызова.
  • Новая позиция x фиксируется в состоянии только после успешного завершения вызова целевой функции.
  • Если функция двигателя возбуждает исключение, оно пробрасывается наружу как исходный объект ошибки, а сохранённая координата остаётся прежней.
  • При успешном выполнении двигателя обёртка возвращает его оригинальный результат (включая None, пустые коллекции и изменяемые объекты).

Каждая декорированная функция может последовательно вызываться от 0 до 1000 раз.

Ввод и вывод

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

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

moves = []
@directional_steps(1, 2)
def motor(x):
    moves.append(x)
    return x
print(motor(2))
try:
    motor(2)
except ValueError:
    print('rejected')
print(motor(4))
print(moves)

Вывод:

2
rejected
4
[2, 4]
РешениеPython

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

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

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