Программирование и IT
Python: декораторы
Декоратор — это функция, которая принимает другую функцию и возвращает новую, с добавленным поведением. Знак `@` перед определением — просто короткая запись присваивания. Если держать это в голове, вся тема разбирается за один вечер.
В этой статье
Функция — такое же значение, как число#
Без этого дальше не пойдёт: функцию в Python можно положить в переменную,
передать аргументом и вернуть из другой функции.
return
= # без скобок — сама функция, не результат
# ПРИВЕТ
# ['A', 'B']
Скобки означают вызов. f = shout — это ссылка, f = shout("x") — результат
вызова. Путаница между ними — источник половины ошибок в этой теме.
Простейший декоратор#
Декоратор принимает функцию, определяет внутри обёртку и возвращает её.
=
return
return
return +
# вызов add
# результат 5
# 5
Запись @loud над def add равнозначна строке add = loud(add) после
определения. Ничего больше в ней нет.
Обёртка принимает *args, **kwargs, чтобы подойти к функции с любой
сигнатурой, и обязательно возвращает результат — иначе декорированная функция
начнёт молча отдавать None. Это ошибка, которую ищут дольше всего.
Сохранить имя функции#
Без дополнительных мер декорированная функция подменяется обёрткой, и об этом
узнаёт всё, что смотрит на имя и документацию:
# wrapper
Лечится одной строкой — functools.wraps копирует имя, документацию и
аннотации из исходной функции в обёртку:
return
return
"""Сложить два числа."""
return +
# add | Сложить два числа.
Декоратор с аргументами#
Когда у декоратора есть собственные настройки, добавляется третий уровень:
внешняя функция принимает настройки и возвращает сам декоратор.
=
return
return
return
return
# привет / привет / привет / ок
Отсюда правило: декоратор с аргументами пишется со скобками — @repeat(3), а
без аргументов скобок нет — @loud. Перепутать легко: @repeat без скобок
передаст саму функцию в параметр times, и падение случится не при
объявлении, а при первом вызове.
Порядок и готовые декораторы#
Несколько декораторов применяются снизу вверх — ближний к def оборачивает
первым:
...
# равнозначно f = a(b(f))
В стандартной библиотеке уже есть полезные декораторы. functools.lru_cache
запоминает результаты вызовов:
return
# 832040
# 28
Без кеша тот же fib(30) считался бы больше миллиона раз. Ещё из привычных —
@property, @staticmethod и @classmethod, которые применяются к методам
и разобраны в статье про классы.
Практика на полчаса: напиши декоратор timed, который печатает время
выполнения через time.perf_counter, и декоратор retry(times), повторяющий
вызов при исключении, — второй опирается на
обработку ошибок.
План по этапам
- Функция в переменнойПрисвоить функцию переменной без скобок, вызвать через новое имя, сравнить с присваиванием результата.
- Обёртка вручнуюНаписать wrapper и применить его строкой f = deco(f), без знака @.
- Перевести на @Заменить ручное присваивание на декоратор и убедиться, что поведение то же.
- Добавить functools.wrapsПроверить __name__ до и после добавления wraps.
- Декоратор с параметромСделать repeat(times) и вызвать функцию с разными значениями параметра.
Начать изучать эту тему у себя
План ляжет в твой репозиторий: отмечай этапы, веди конспект — история изменений покажет, как ты продвинулся.
Проверь себя
1.Знак @deco над def f() равнозначен какой строке?
2.Обёртка вызывает func(*args), но не пишет return. Что вернёт декорированная функция?
3.Сколько раз напечатается «привет» при вызове функции, украшенной @repeat(times=3)?
4.В записи «@a / @b / def f()» какой декоратор оборачивает функцию первым — a или b?
Источники
-
Модуль functoolswraps, lru_cache, cache и другие готовые обёрткибесплатно
-
Справочник языка: декораторыФормальное описание, во что разворачивается знак @бесплатно
-
StepikКурсы по Python, где декораторы идут с автопроверкойбесплатно
Было полезно?