Класс-декоратор Python: переключение между основной и резервной репликой
Напишите решение на Python и проверьте его на странице.
Другие упражнения по этой теме: Классы-декораторы Python: задачи на call, методы и состояние.
Маршрутизатор реплик
Сервис аналитики запрашивает отчёты через функцию обращения к хранилищу и распределяет нагрузку между узлами, передавая имя источника в строковом аргументе source. Чтобы разгружать основной узел, вызовы организуют в повторяющиеся серии: ровно успешных обращений подряд отправляются с аргументом 'primary', а сразу после них на один вызов подставляется 'replica', после чего отсчёт серии начинается заново.
Если обращение к любому из узлов завершается исключением, счётчик серии не продвигается и не обнуляется, а сама ошибка пробрасывается вызывающему коду. Для внеочередных проверок у декоратора предусмотрен метод force_replica(). Он помечает ровно следующий одиночный вызов как принудительный: функция выполняется с аргументом 'replica', и только при её успехе серия к основному узлу сбрасывается. Если же этот принудительный вызов падает с ошибкой, выставленная метка снимается, а накопленный ранее прогресс сохраняется без изменений.
Реализуйте класс-декоратор для такой циклической диспетчеризации, поддерживающий метод принудительного переключения и сохраняющий состояние серии при сбоях.
Что уже дано
В редакторе объявлен класс ReplicaRouter с заготовками методов __init__, __call__ и force_replica.
Что нужно сделать
Реализуйте класс ReplicaRouter(function, quota) со следующим поведением:
- Конструктор принимает синхронную вызываемую функцию
function, ожидающую именованный параметрsource, и целое положительное числоquotaот до — квоту успешных вызовов к основному узлу в рамках одного цикла. - Метод
__call__(*args, **kwargs)вызывает сохранённую функцию, передавая все позиционные и именованные аргументы без изменений, но подставляя или переопределяя именованный аргументsourceзначением'primary'либо'replica': - Первые успешных вызовов цикла выполняются с
source='primary'. - После успешных основных вызовов следующий обычный вызов выполняется с
source='replica'. - Только успешное завершение вызова с
'replica'начинает отсчёт следующего цикла основных вызовов заново (счётчик успешных основных вызовов сбрасывается в ). - Любое исключение (включая прямые подклассы
BaseException), возникшее внутри целевой функции, не изменяет и не обнуляет накопленный прогресс и беспрепятственно пробрасывается наружу как тот же самый объект ошибки. При успешном выполнении метод возвращает исходный объект результата целевой функции. - Метод
force_replica()возвращаетNone, не вызывает целевую функцию и выставляет флаг принудительной маршрутизации ровно на один следующий вызов__call__: - Повторные вызовы
force_replica()до следующего обращения к декоратору имеют такой же эффект, как и один вызов. - Следующий вызов
__call__выполняется сsource='replica'независимо от текущего прогресса цикла, а флаг принудительного переключения немедленно снимается (в том числе при ошибке вызова). - Если принудительный вызов завершился успешно, счётчик основных вызовов цикла сбрасывается в . Если же принудительный вызов завершился ошибкой, накопленный ранее прогресс сохраняется без изменений.
- Если флаг был выставлен в момент, когда квота уже была полностью набрана, и этот принудительный вызов завершился ошибкой, следующий обычный вызов всё равно автоматически направляется к
'replica'по общему правилу исчерпания квоты. - Состояние полностью изолировано для каждого экземпляра
ReplicaRouter. Каждый экземпляр может обрабатывать от до вызовов, а система может использовать от до независимых экземпляров одновременно.
Ввод и вывод
Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля класс верхнего уровня ReplicaRouter и вызывает его с проверочными аргументами.
Пример использования
def fetch(report, *, source):
return (report, source)
router = ReplicaRouter(fetch, 2)
print(router("a"))
print(router("b"))
print(router("c"))
router.force_replica()
print(router("d"))
print(router("e"))
Вывод:
('a', 'primary')
('b', 'primary')
('c', 'replica')
('d', 'replica')
('e', 'primary')
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Связанные задачи
- Класс-декоратор Python: когда вызывается init
- Класс-декоратор метода Python: почему теряется self
- Класс-декоратор Python: ограничение изменения результата между вызовами
- Класс-декоратор Python: баланс доступных вызовов функции
- Класс-декоратор Python: изменение аргумента первых N вызовов
- Класс-декоратор Python: буфер событий и пакетная запись
