Декораторы в Python — это мощный инструмент для расширения функций без изменения их кода. Они позволяют добавлять логику вроде логирования, кэширования или проверки доступа, делая код чище и повторно используемым.
Зачем они нужны? Представь, что у тебя есть функция для вычисления, и ты хочешь каждый раз измерять её время или логировать вызовы. Вместо копипасты кода везде просто пишешь декоратор и вешаешь его одной строкой. Это решает проблему дублирования и упрощает поддержку.
Что такое декоратор на самом деле
Декоратор — это функция, которая принимает другую функцию как аргумент, оборачивает её в новую логику и возвращает обновлённую версию. Всё происходит автоматически с помощью синтаксиса @имя_декоратора. Это как обёртка: внешний слой добавляет поведение, а внутри остаётся оригинальная функция.
Рассмотрим базовый пример. Допустим, у нас есть простая функция say_hi, которая возвращает строку “всем привет”. Декоратор сделает буквы заглавными, не трогая код функции. Оборачиваем функцию во внутреннюю wrapper, выполняем нужную логику до и после вызова оригинала — и готово. Такой подход работает для любых функций, даже с параметрами.
Вот как выглядит структура:
- Декоратор принимает func.
- Внутри создаёт wrapper, которая вызывает func с аргументами.
- Wrapper возвращает результат func плюс добавленная логика.
def uppercase_decorator(func):
def wrapper():
result = func()
return result.upper()
return wrapper
@uppercase_decorator
def say_hi():
return "всем привет"
print(say_hi()) # ВСЕМ ПРИВЕТ
Ключевой момент: *wrapper должна принимать *args и *kwargs, чтобы декоратор работал с любыми функциями.
Простые примеры декораторов в действии
Начнём с логирования. Представь функцию, которая печатает имя. Декоратор перед вызовом выведет “Функция вызвана”. Это полезно для отладки: видишь стек вызовов без if-ов повсюду. Аналогично можно добавить timestamp или уровень логирования.
Другой пример — замер времени. Функция вычисляет сумму списка, а декоратор покажет, сколько прошло миллисекунд. Импортируешь time, фиксируешь start и end в wrapper — и вуаля. Идеально для оптимизации кода.
Переходим к деталям. Вот типичные случаи:
- Логирование: print(f"Вызвана {func.name}")
- Время выполнения: time.perf_counter() до и после
- Проверка аргументов: raise ValueError, если не ок
| Пример декоратора | Что добавляет | Когда использовать |
|---|---|---|
| Логирование | Сообщение о вызове | Отладка, мониторинг |
| Тайминг | Время в мс | Профилирование |
| Кэширование | Сохранение результата | Дорогие вычисления |
Эти примеры показывают, как декораторы упрощают жизнь. Не забывай возвращать результат func, иначе функция ничего не отдаст.
Декораторы с параметрами и несколько штук сразу
Обычный декоратор не принимает аргументы сам по себе. Чтобы добавить параметры, создай фабрику: внешняя функция принимает настройки, внутри — настоящий декоратор с wrapper. Например, декоратор повторений: @retry(times=3) — попробует функцию 3 раза при ошибке.
С несколькими декораторами порядок важен. Они применяются снизу вверх: первый ближе к функции оборачивает её, следующий — результат предыдущего. Если @log над @time, то сначала time, потом log. Это накапливает эффекты: время + лог + валидация.
Структура декоратора с параметрами:
- Внешняя функция repeat(n):
- Возвращает decorator(func)
- Decorator возвращает wrapper с циклом на n попыток
- Wrapper ловит исключения и retry
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
try:
return func(*args, **kwargs)
except:
pass
raise
return wrapper
return decorator
@repeat(3)
def risky_func():
# может упасть
pass
Важно: сохраняй метаданные func с functools.wraps(func) — имя, докстринг останутся правильными.
Декораторы на классах и продвинутые фичи
Не только функции: классы с init и call тоже декораторы. В init сохраняешь func, в call — выполняешь логику. Полезно для состояний, как счётчик вызовов или кэш в атрибуте.
Применение: мемоизация. Декоратор хранит результаты в словаре {args: result}. При повторном вызове с теми же аргументами — берёт из кэша. Идеально для рекурсии Фибоначчи или API-запросов.
Продвинутые примеры:
- Мемоизация: @lru_cache из functools
- Состояние в классе: self.count +=1
- Для методов: self-aware wrapper
| Тип декоратора | Преимущества | Ограничения |
|---|---|---|
| Функция | Просто | Нет состояния |
| Класс | Состояние | Словеснее |
| functools | Готовые | Меньше контроля |
Классовые декораторы хороши, когда нужна память между вызовами.
Когда декораторы меняют правила игры
Декораторы — это не просто сахар: они принцип SOLID в действии. Модифицируешь поведение без изменения класса или функции, делая код модульным. В веб-фреймворках вроде Flask они проверяют роли, в тестах — мокнут зависимости.
Осталось место для размышлений: как комбинировать с async/await или генераторами? Или создать декоратор для валидации схем с pydantic. Подумай над своими проектами — наверняка найдётся, где упростить логику.
Это базовый набор, чтобы стартовать уверенно. Дальше экспериментируй с реальными задачами.
— это