ПрограммированиеPython Backend разработчик

Что такое декораторы для функций в Python, какова их история и зачем они применяются в современном программировании?

Проходите собеседования с ИИ помощником Hintsage

Ответ

История вопроса

Декораторы появились в Python как синтаксический сахар с версии 2.4, чтобы облегчить работу с функциями высшего порядка — такими, которые принимают или возвращают другие функции. Эволюция подходов к расширению функциональности функций привела к формату лаконичных и выразительных средств — аннотаций через @decorator.

Проблема

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

Решение

Декораторы позволяют выносить повторяемые аспекты в отдельные обёртки, тем самым повышая читаемость и переиспользуемость кода. С помощью @decorator можно элегантно добавлять функциональность к функциям и методам:

Пример кода:

import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # Выведет, сколько заняло выполнение

Ключевые особенности:

  • Позволяют переиспользовать код (DRY-principle);
  • Могут использоваться для функций, методов, классов;
  • Позволяют внедрять кросс-функциональные задачи (логирование, кеш, доступ, профилирование) централизованно;

Вопросы с подвохом.

Могут ли декораторы менять сигнатуру оборачиваемой функции?

Часто ошибочно считают, что сигнатура функции декоратором не меняется. На самом деле без использования модуля functools.wraps метаинформация исчезает, что может вызвать неожиданные ошибки в автодокументации или introspection.

Пример кода:

from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper

Могут ли декораторы быть параметризированы?

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

Пример кода:

def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")

Можно ли применять несколько декораторов к одной функции? Какой будет порядок исполнения?

Часто ошибочно полагают, что порядок не важен или он совпадает с порядком размещения декораторов в коде. На самом деле сначала применяется самый нижний декоратор, затем следующий и так далее вверх.

Пример кода:

def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core

Типовые ошибки и анти-паттерны

  • Игнорирование functools.wraps теряет метаинформацию об оригинале функции;
  • Логика декоратора не учитывает исключения, нельзя поймать и обработать ошибку внутри;
  • Неочевидное наложение нескольких декораторов

Пример из жизни

Негативный кейс

Логирование времени работы добавлено к 10 функциям вручную, копированием-кодированием.

Плюсы:

  • Логика ясна, код рядом, легко найти ошибку.

Минусы:

  • Сложно поддерживать — потребуется менять десятки участков, если нужно изменить поведение.
  • Код дублируется, нарушается DRY.

Позитивный кейс

Вся логика тайминга вынесена в декоратор, все функции, где нужна эта метрика, обёрнуты декоратором @timing_decorator.

Плюсы:

  • Изменения делаются централизованно;
  • Код короче и читаемее.

Минусы:

  • Возможна потеря информации о сигнатуре без functools.wraps, если не внимателен;
  • Новичкам сложнее сходу понять механику работы декораторов.