Декораторы
Представьте подарок в упаковочной бумаге. Подарок внутри тот же, но, чтобы до него добраться, сначала разворачивают бумагу. В Python так же можно «упаковать» функцию: каждый вызов сначала проходит через упаковку и только потом доходит до самой функции.
Зачем это нужно? Перед каждым вызовом нескольких функций нужно печатать строку calling. Можно дописать print в тело каждой из них, но тогда одна и та же строка повторится во многих местах. Вместо этого функцию передают другой функции, а та возвращает новую, с добавленным поведением:
def announce(func):
def wrapper(*args, **kwargs):
print("calling")
return func(*args, **kwargs)
return wrapper
def add(a, b):
return a + b
add = announce(add)
result = add(2, 3) # calling
print(result) # 5Давайте разберём пример по строкам:
add = announce(add)отдаёт функциюaddвannounceи получает назадwrapper. Теперь имяaddуказывает на неё.add(2, 3)вызываетwrapper, и та печатаетcalling.wrapperвызывает исходнуюaddс теми же аргументами и возвращает её результат5.print(result)печатает5.
Обёртка
Функцию, которая принимает функцию и возвращает новую, называют декоратором. Функция, которую он возвращает, — обёртка: та самая упаковочная бумага.
Здесь wrapper — замыкание: она помнит func, исходную add. При вызове обёртка печатает calling, затем вызывает исходную функцию и возвращает её результат.
Параметры *args и **kwargs позволяют обёртке принять любые аргументы и передать их исходной функции без изменений. Поэтому announce подходит для функции с любыми параметрами. До и после вызова обёртка может делать что угодно:
- печатать строку;
- считать вызовы;
- менять результат.
Строка add = announce(add) вызывает announce один раз. Тело add при этом не выполняется: announce получает функцию, а не результат её вызова. После присваивания каждый вызов add идёт через обёртку. Исходная функция доступна только самой обёртке, через func.
Здесь аналогия с подарком заканчивается. Настоящую бумагу снимают один раз, а обёртка срабатывает при каждом вызове. К тому же на обратном пути она может подменить результат — с подарком так не бывает.
Запись @
Строка @announce над def делает то же самое короче. Она равна присваиванию greet = announce(greet) сразу после def:
@announce
def greet(name):
return f"hi {name}"
message = greet("ann") # calling
print(message) # hi annОдин декоратор подходит многим функциям: строку @announce ставят над каждой из них, а print написан один раз, внутри wrapper.
Декоратор срабатывает один раз, когда Python выполняет def, а не при каждом вызове. Сама announce при этом ничего не печатает. calling появляется при каждом вызове greet, потому что его печатает обёртка.
Этот вопрос часто задают на собеседовании: «Что такое декоратор и чему равна запись @dec?» Ответ из двух частей:
- декоратор — функция, которая принимает функцию и возвращает другую, обычно обёртку;
@decнадdef f— краткая записьf = dec(f), и имяfдальше указывает на то, что вернулdec.
Частая ошибка
В обёртке забыли return:
def announce(func):
def wrapper(*args, **kwargs):
print("calling")
func(*args, **kwargs) # ✗
return wrapper
@announce
def add(a, b):
return a + b
print(add(2, 3))Программа печатает calling, а затем None вместо 5. Исходная add вернула 5 обёртке, но обёртка его не вернула, а функция без return возвращает None. Пишите return func(*args, **kwargs).
Главное
- Декоратор принимает функцию и возвращает обёртку, которая добавляет поведение до или после вызова.
@decнадdef f— то же, чтоf = dec(f)сразу после определения.- Обёртка с
*args, **kwargsподходит функции с любыми параметрами. - Обёртка должна вернуть результат исходной функции, иначе вызов вернёт
None.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.