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

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

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

Ответ

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

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

Проблема

Обычные декораторы принимают только одну функцию, которую нужно обернуть. Когда требуется передать самому декоратору параметры, синтаксис становится сложнее, что часто приводит к ошибкам, особенно при вложенности функций и пробросе *args/**kwargs.

Решение

Декоратор с параметрами реализуется функцией более высокого порядка: сначала вызывается внешняя "декорирующая" функция с аргументами, которая создаёт и возвращает сам декоратор, а декоратор уже получает функцию и возвращает обёртку:

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 greet(name): print(f"Hello, {name}!") greet("Python") # Вывод: Hello, Python! (3 раза)

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

  • Декоратор с параметрами всегда реализуется через тройную вложенность функций
  • Wrapper должен возвращать результат, корректно пробрасывать *args/**kwargs
  • Не забывайте про functools.wraps для сохранения метаданных

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

Почему нельзя реализовать декоратор с параметрами так же, как обычный декоратор?

Если вы применяете @decorator, Python передаёт функцию в качестве аргумента декоратору. Если добавляете скобки (@decorator()), Python сначала вызывает функцию, и только её результат интерпретируется как декоратор.

def deco(func): # обычный декоратор: @deco def deco_with_args(arg): # декоратор с аргументом: @deco_with_args(arg)

Чем принципиально отличаются декораторы с аргументами от декораторов без аргументов на уровне вызова?

Декораторы без аргументов получают на вход функцию, декораторы с аргументами — не функцию, а параметры, и возвращают из себя декоратор.

Как правильно применять functools.wraps и зачем это делать?

functools.wraps(func) в обёртке сохраняет имя, строку документации и другие метаданные оригинальной функции, иначе все эти сведения заменятся у wrapper, что мешает отладке и introspection.

import functools def deco_with_args(arg): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator

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

  • Забывают о необходимости трёх вложенных функций, делают только две (или вообще одну), результат не будет декоратором
  • Не пробрасывают *args/**kwargs вовнутрь wrapper
  • Теряют метаинформацию функции вследствие отсутствия functools.wraps

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

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

Реализовали декоратор без учёта параметров или с неверным количеством вложенных функций:

def log(level): def wrapper(func): # ошибка — wrapper должен быть глубже print(f"Log: {level}") func() # Функция не возвращается как декоратор return wrapper @log("INFO") def action(): print("Work!")

Плюсы:

  • Просто выглядит

Минусы:

  • Декоратор не работает, функция вызывается на этапе декорирования, а не при вызове action()

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

Использование functools.wraps и корректных вложенных функций:

import functools def timer(units): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) end = time.time() if units == 'ms': duration = (end - start) * 1000 else: duration = end - start print(f"Duration: {duration:.4f} {units}") return result return wrapper return decorator @timer('ms') def op(): sum(range(1000)) op()

Плюсы:

  • Корректная структура, удобно расширять, чистые логи

Минусы:

  • Сложнее читается, особенно для новичков