Каково назначение декоратора functools.wraps в Python?

Ответ

Декоратор @functools.wraps используется для того, чтобы функция-обертка, создаваемая декоратором, наследовала метаданные (имя, docstring, аннотации типов, аргументы) от исходной декорируемой функции.

Проблема, которую он решает:

Без @wraps метаданные оригинальной функции заменяются метаданными функции-обертки. Это затрудняет отладку (например, в стектрейсах отображается имя wrapper), а также нарушает работу инструментов для интроспекции и автоматической генерации документации.

Пример без @wraps:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        """Это документация обертки."""
        print("Что-то до вызова функции.")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello():
    """Это документация оригинальной функции."""
    print("Привет!")

print(say_hello.__name__)  # Выведет: 'wrapper'
print(say_hello.__doc__)   # Выведет: 'Это документация обертки.'

Пример с @wraps:

from functools import wraps

def my_decorator(func):
    @wraps(func) # Копируем метаданные из func в wrapper
    def wrapper(*args, **kwargs):
        """Это документация обертки (игнорируется благодаря @wraps)."""
        print("Что-то до вызова функции.")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello():
    """Это документация оригинальной функции."""
    print("Привет!")

print(say_hello.__name__)  # Выведет: 'say_hello'
print(say_hello.__doc__)   # Выведет: 'Это документация оригинальной функции.'

Ответ 18+ 🔞

Давай я тебе объясню про этот самый @functools.wraps, а то без него декораторы превращаются в такую жесть, что потом сам себя не узнаешь в отладчике, блядь.

Представь, ты написал декоратор — обёртку для функции. Красиво, модно, молодёжно. Но без @wraps твоя обёртка начинает вести себя как полная пиздопроебина: она крадёт у оригинальной функции её имя, её документ-стринг, её аннотации — всё, что делает её узнаваемой. В итоге в стектрейсе вместо родного имени функции ты видишь какого-то унылого wrapper, а документация твоя летит в пизду.

Вот смотри, как это выглядит без этого самого @wraps:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        """Это документация обертки."""
        print("Что-то до вызова функции.")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello():
    """Это документация оригинальной функции."""
    print("Привет!")

print(say_hello.__name__)  # Выведет: 'wrapper'
print(say_hello.__doc__)   # Выведет: 'Это документация обертки.'

Видишь? Функция say_hello теперь зовётся wrapper, а её родная документация подменена на какую-то хуйню. Это как если бы Герасим из той истории вместо «Муму» начал орать «Я — Герасим, блядь!». Полный пиздец для интроспекции.

А теперь смотрим, как надо делать, с @wraps:

from functools import wraps

def my_decorator(func):
    @wraps(func) # Вот эта магия копирует метаданные из func в wrapper
    def wrapper(*args, **kwargs):
        """Это документация обертки (игнорируется благодаря @wraps)."""
        print("Что-то до вызова функции.")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello():
    """Это документация оригинальной функции."""
    print("Привет!")

print(say_hello.__name__)  # Выведет: 'say_hello'
print(say_hello.__doc__)   # Выведет: 'Это документация оригинальной функции.'

Вот теперь — красота! Функция сохранила своё имя и свою душу — документацию. @wraps просто аккуратно копирует все метаданные из оригинальной функции в обёртку, и все инструменты для отладки или генерации документации работают как надо. Без этой штуки ты будешь как тот немой Герасим — будешь мычать «Муму», а тебя никто не поймёт, блядь.