Короткий ответ
functools.wraps копирует метаданные оригинальной функции (__name__, __doc__, __module__ и др.) на wrapper, чтобы декорированная функция выглядела как оригинал для инструментов отладки и интроспекции.
Как это работает подробнее
Без wraps wrapper — отдельная функция со своим __name__ ("wrapper") и пустым __doc__, что сбивает инструменты, читающие метаданные (help(), документация, трассировка ошибок). functools.wraps(func) копирует нужные атрибуты с func на wrapper перед возвратом.
Пример кода
example-246.py
import functools
def without_wraps(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def with_wraps(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@without_wraps
def foo(): pass
@with_wraps
def bar(): pass
print(foo.__name__) # "wrapper" - метаданные потеряны
print(bar.__name__) # "bar" - метаданные сохраненыЧто использовать на практике
- Практика.
wrapsиспользуют почти всегда при написании декораторов общего назначения, особенно в библиотечном коде, где корректные имена функций важны для логов, генерации OpenAPI-схем по именам хендлеров, трассировки ошибок.
Подводные камни
- Риски. Забывают про
wraps, из-за чего в stack trace и логах видно неинформативное имя "wrapper"; не задумываются, чтоinspect.signatureбезwrapsпокажет(*args, kwargs)вместо настоящей сигнатуры функции.