В Python декоратор функции высшего порядка — это функция, принимающая другую функцию (или класс) и возвращающая новую функцию (или новый модифицированный класс). Декораторы часто используют для реализации паттерна "обёртывания" (wrapping), позволяющего добавлять дополнительную логику (например, логирование, кеширование, проверку прав и т.д.) к уже существующим функциям без изменения их исходного кода.
Для сохранения имени, документации и других метаданных оригинальной функции рекомендуется использовать функцию functools.wraps:
import functools def log_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'Calling {func.__name__}') return func(*args, **kwargs) return wrapper @log_decorator def add(a, b): """Add two numbers""" return a + b print(add(2, 3)) # Output: Calling add 5 print(add.__name__) # Output: add print(add.__doc__) # Output: Add two numbers
Ключевой момент: без functools.wraps у обёрнутой функции потеряются имя, документация и прочие метаданные оригинала, что негативно влияет на отладку и автодокументирование.
Если задекорировать функцию без использования functools.wraps, что произойдёт с атрибутами name и doc функции?
Ответ: Они будут унаследованы от внутренней функции-обёртки (обычно 'wrapper'), и вы потеряете оригинальные метаданные.
def simple_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @simple_decorator def f(): """This is docstring""" pass print(f.__name__) # Output: 'wrapper' (НЕ 'f') print(f.__doc__) # Output: None (а не 'This is docstring')
История
В проекте реализовали сложную систему декораторов для журналирования API-endpoint'ов, но не применяли functools.wraps. В результате, автогенерация документации (Swagger/OpenAPI) и инструменты интроспекции показывали имена всех endpoint'ов как 'wrapper', а документация исчезла. Это сильно затрудняло поддержку, тестирование и сопровождение.
История
При написании unit tests с помощью pytest произошёл сбой auto-discovery тестов: тестовые функции, задекорированные своими декораторами без wraps, не обнаруживались, потому что их name был некорректным. Причина — pytest ищет функции по имени.
История
При трассировке стека исключений (traceback) stack-trace указывал всегда на "wrapper", и было невозможно понять, какая именно функция вызвала ошибку, поскольку root-метаданные потерялись из-за отсутствия functools.wraps в пользовательских декораторах.