← Назад к списку
ПрограммированиеPythonMiddle

Напишите декоратор retry(times, delay), который повторяет функцию при исключении.

Короткий ответ

  • Декоратор с параметрами — фабрика, возвращающая декоратор
  • Обёртка ловит исключение и повторяет вызов в цикле
  • После последней попытки исключение пробрасывается
  • functools.wraps сохраняет метаданные функции
  • Лучше ловить конкретные исключения, не Exception
  • В проде добавляют экспоненциальную задержку и джиттер

Трёхуровневая конструкция: фабрика принимает параметры, декоратор — функцию, обёртка повторяет вызов в цикле и пробрасывает ошибку после последней попытки.

Как сказать вслух

пример ответа

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

Подробный ответ

Основной ответ

Структура: retry(times, delay) — фабрика, которая захватывает параметры в замыкание и возвращает декоратор; декоратор оборачивает функцию в wrapper с functools.wraps. Wrapper крутит цикл до times попыток: успешный вызов сразу возвращает результат; при исключении из белого списка — пауза time.sleep(delay) и следующая попытка; на последней итерации исключение не глотается, а пробрасывается (raise). Важные решения: параметр exceptions с кортежем ловимых типов (по умолчанию узкий, например ConnectionError), логирование каждой неудачи, и то, что повтор уместен только для идемпотентных операций. Развитие — экспоненциальный backoff delay * 2**attempt с джиттером; для asyncio пишется параллельная async-версия с asyncio.sleep.

Ключевые моменты

  • Три уровня замыканий. Фабрика хранит параметры, декоратор — функцию, обёртка исполняет логику; @retry(3, 1.0) — вызов фабрики.
  • Проброс после попыток. Последняя неудача должна поднять оригинальное исключение, иначе вызывающий код получит молчаливый None.
  • Узкие исключения. Ловить TypeError или KeyError и повторять — маскировка багов; повторяют временные сбои I/O.
  • Backoff и идемпотентность. Экспоненциальная пауза с джиттером снижает нагрузку на падающий сервис; повторять POST-платёж нельзя.

Практический контекст

Это любимая задача на секциях live coding: она проверяет замыкания, *args/**kwargs, работу с исключениями и инженерное чутьё одновременно. В продакшене такие декораторы применяют к HTTP-вызовам и работе с очередями, а чаще берут готовую библиотеку tenacity — упоминание её показывает практический опыт.

Пример кода

import functools, time

def retry(times=3, delay=0.5, exceptions=(ConnectionError, TimeoutError)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    if attempt == times:
                        raise
                    time.sleep(delay * attempt)
        return wrapper
    return decorator

@retry(times=3, delay=0.2)
def fetch_data():
    ...  # сетевой вызов

Частые ошибки

  • Глотают исключение после последней попытки и возвращают None
  • Ловят голый Exception, повторяя в том числе ошибки логики
  • Забывают паузу между попытками или functools.wraps на обёртке

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru