Python: декоратор @retry



Книга Python: декоратор @retry

Python часто называют “склеивающим” языком. Для меня этот термин означает, что язык помогает соединять системы и обеспечивает передачу данных из A в B в желаемой структуре и формате.


Я создал бесчисленное количество ETL-скриптов  —  Extraction Transformation Load — извлечение, преобразование, загрузка на Python. Все эти сценарии работают по сути по одному и тому же принципу: откуда-то извлекают данные, преобразуют их и затем выполняют последнюю операцию. Последней операцией обычно бывает загрузка данных куда-либо, но также может быть условное удаление.


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


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


Я обнаружил, что очень простой декоратор retry может стать спасением в подобных ситуациях. Большинство моих проектов в тот или иной момент в итоге содержат декоратор retry в каком-либо утилитном модуле.


Декоратор


Функции — это объекты первого уровня


В Python функции являются объектами первого уровня. То есть функция  —  это тоже объект. Этот факт помимо всего прочего означает, что функцию можно динамически создавать, передавать в саму эту функцию и даже изменять. Взгляните на простейший пример:


def my_function(x):
print(x)

IN:
my_function(2)
OUT:
2

IN:
my_function.yolo = 'you live only once'
print(my_function.yolo)
OUT:
'you live only once'

Декорирование функции


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


def first_func(x):
return x**2

def second_func(x):
return x - 2

Обе функции завершатся ошибкой при вызове со строкой '2'. Мы можем добавить функцию преобразования типа и декорировать этой функцией first_func и second_func.


def convert_to_numeric(func):


# определяем функцию во внешней функции
def new_func(x):
return func(float(x))

# возвращаем вновь определённую функцию
return new_func

Эта функция-обёртка convert_to_numeric требует другую функцию в качестве аргумента и возвращает другую функцию. Теперь, когда мы оборачиваем функции и вызываем их со строковым числом, всё работает:


IN:
new_fist_func = convert_to_numeric(first_func)

###############################
convert_to_numeric возвращает эту функцию:
defnew_func(x):
returnfirst_func(float(x))
###############################

new_fist_func('2')
OUT:
4.0

IN:
convert_to_numeric(second_func)('2')
OUT:
0

Что здесь происходит?


Наша convert_to_numeric принимает функцию (A) в качестве аргумента и возвращает новую функцию (B). Новая функция (B) при вызове вызывает функцию (A), но не с переданным аргументомx, а с float(x), и таким образом решает проблему TypeError.


Синтаксис декоратора


Для упрощения работы Python предоставляет специальный синтаксис:


@convert_to_numeric
def first_func(x):
return x**2

Синтаксис выше эквивалентен этому коду:


def first_func(x):
return x**2

first_func = convert_to_numeric(first_func)

Развёрнутый синтаксис проясняет, что именно здесь происходит, особенно при применении нескольких декораторов.


Retry!


Теперь, когда мы разобрались в основах, давайте перейдём к моему любимому и широко используемому декоратору retry:


Код здесь!

Заворачиваем функцию. Не переживайте, всё не так уж сложно. Пройдём по коду шаг за шагом:


  1. Самая первая функция retry параметризует декоратор, то есть указывает, какие исключения мы хотим обработать, частоту попыток, интервал ожидания между попытками и каков экспоненциальный фактор возврата  —  число, на которое умножается время ожидания каждый раз при неудачной попытке.
  2. retry_decorator: это параметризованный декоратор, который возвращается функцией retry. Мы декорируем функцию в retry_decoratorс помощью @wraps. Строго говоря, это не так уж необходимо, когда речь идёт о функциональности. Эта функция-обёртка обновляет __name__ и __doc__ обёрнутой функции: если этого не сделать, функция __name__ всегда будет func_with_retries).
  3. func_with_retries применяет логику повтора. Эта функция оборачивает вызовы в блоки try-except и реализует экспоненциальное ожидание возврата с некоторым логированием. 

Применение


Функция, декорированная с помощью retry, предпринимающим четыре попытки до любого исключения

Как альтернатива, немного более конкретно:


Функция, декорированная retry в ответ на TimeoutError предпринимает две попытки 

Результаты:


Вызов декорированной функции и столкновение с ошибками приведёт к следующему:



Вызываемая функция дважды завершилась с ошибкой ConnectionRefusedError, один раз с ConnectionResetError и успешно выполнилась с четвёртой попытки.


Здесь у нас есть информативное логирование, мы отображаем args и kwargs и имя функции, что облегчает отладку и исправление ошибок в случае, когда ошибка не устраняется после всех попыток.


Заключение


Мы разобрали, как применять декораторы в Python и как декорировать критически важные функции декоратором retry, чтобы они выполнялись даже в условиях неопределённости.


478   0  

Comments

    Ничего не найдено.