← Назад к вопросам

Для чего используется functools.wraps в декораторах?

2.0 Middle🔥 151 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

functools.wraps в декораторах

functools.wraps - это декоратор, который сохраняет метаинформацию исходной функции (имя, документация, аннотации) при её обёртывании другой функцией. Без него декоратор "скрывает" оригинальную функцию.

Проблема без functools.wraps

def my_decorator(func):
    def wrapper(*args, **kwargs):
        """Обёртка функции"""
        print(f"Вызываю {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(name: str) -> str:
    """Функция приветствия"""
    return f"Hello, {name}!"

# Проблема: метаинформация потеряна!
print(greet.__name__)  # wrapper (должно быть greet)
print(greet.__doc__)   # Обёртка функции (должно быть Функция приветствия)
print(greet.__module__)  # Теряется и модуль
print(greet.__annotations__)  # {} (теряются аннотации типов)

Это проблемно для:

  • Документации (help() покажет неправильно)
  • Отладки (стек вызовов будет "wrapper")
  • Тестирования (не сможешь найти функцию по имени)
  • IDE автодополнения (потеряются типы)

Решение с functools.wraps

import functools

def my_decorator(func):
    @functools.wraps(func)  # Волшебная строка!
    def wrapper(*args, **kwargs):
        """Обёртка функции"""
        print(f"Вызываю {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(name: str) -> str:
    """Функция приветствия"""
    return f"Hello, {name}!"

# Теперь всё правильно!
print(greet.__name__)  # greet
print(greet.__doc__)   # Функция приветствия
print(greet.__annotations__)  # {name: <class str>, return: <class str>}
print(greet.__module__)  # __main__

Что копирует functools.wraps

# По умолчанию копируются эти атрибуты:
# __module__      - модуль функции
# __name__        - имя функции
# __qualname__    - полное имя (для методов)
# __annotations__ - аннотации типов
# __doc__         - docstring
# __dict__        - пользовательские атрибуты
# __wrapped__     - ссылка на оригинальную функцию

import functools

def timing_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

@timing_decorator
def slow_function(x: int) -> int:
    """Медленная функция для примера"""
    import time
    time.sleep(0.1)
    return x * 2

print(slow_function.__name__)  # slow_function
print(slow_function.__wrapped__)  # <function slow_function at ...>

Практические примеры

1. Декоратор логирования

import functools
import logging

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"Вызов: {func.__name__}{args}{kwargs}")
        try:
            result = func(*args, **kwargs)
            logging.info(f"Результат: {result}")
            return result
        except Exception as e:
            logging.error(f"Ошибка: {e}")
            raise
    return wrapper

@log_calls
def calculate(a: int, b: int) -> int:
    """Складывает два числа"""
    return a + b

calculate(5, 3)
print(calculate.__name__)  # calculate
print(help(calculate))  # Покажет правильную документацию!

2. Декоратор кеширования

import functools

def memoize(func):
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    
    return wrapper

@memoize
def fibonacci(n: int) -> int:
    """Вычисляет n-е число Фибоначчи"""
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci.__name__)  # fibonacci
print(fibonacci.__doc__)  # Вычисляет n-е число Фибоначчи
print(fibonacci(10))  # Работает с кешем

3. Декоратор с параметрами

import functools

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                results.append(func(*args, **kwargs))
            return results
        return wrapper
    return decorator

@repeat(times=3)
def get_random():
    """Возвращает случайное число"""
    import random
    return random.randint(1, 100)

print(get_random.__name__)  # get_random
print(get_random())  # [45, 82, 23]

4. Декоратор для типов (type hints)

import functools
from typing import Any

def validate_types(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Можем проверить аннотации благодаря functools.wraps
        annotations = func.__annotations__
        # ... валидация типов ...
        return func(*args, **kwargs)
    return wrapper

@validate_types
def divide(a: float, b: float) -> float:
    """Делит a на b"""
    if b == 0:
        raise ValueError("Деление на ноль")
    return a / b

print(divide.__annotations__)  # {a: <class float>, b: <class float>, return: <class float>}

Сравнение: с functools.wraps и без

import functools

# БЕЗ functools.wraps
def decorator_without(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# С functools.wraps
def decorator_with(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator_without
def test_func_without():
    """Тестовая функция"""
    pass

@decorator_with
def test_func_with():
    """Тестовая функция"""
    pass

print("БЕЗ functools.wraps:")
print(f"  __name__: {test_func_without.__name__}")  # wrapper
print(f"  __doc__: {test_func_without.__doc__}")    # None
print(f"  __wrapped__: {hasattr(test_func_without, __wrapped__)}")
# Вывод: False

print("\nС functools.wraps:")
print(f"  __name__: {test_func_with.__name__}")  # test_func_with
print(f"  __doc__: {test_func_with.__doc__}")    # Тестовая функция
print(f"  __wrapped__: {hasattr(test_func_with, __wrapped__)}")
# Вывод: True

Кастомная функция копирования

import functools
from typing import Callable, Any

def my_wraps(wrapped: Callable) -> Callable:
    """Кастомная реализация wraps"""
    def decorator(wrapper: Callable) -> Callable:
        # Копируем нужные атрибуты
        wrapper.__name__ = wrapped.__name__
        wrapper.__doc__ = wrapped.__doc__
        wrapper.__module__ = wrapped.__module__
        wrapper.__annotations__ = wrapped.__annotations__
        wrapper.__wrapped__ = wrapped
        return wrapper
    return decorator

# Примерно такое делает functools.wraps

Правило: ВСЕГДА используй functools.wraps

# Правильно
import functools

def my_decorator(func):
    @functools.wraps(func)  # ← ВСЕГДА ставь!
    def wrapper(*args, **kwargs):
        # ...
        return func(*args, **kwargs)
    return wrapper

# Неправильно (не ставишь functools.wraps)
def bad_decorator(func):
    def wrapper(*args, **kwargs):  # ← Плохо!
        return func(*args, **kwargs)
    return wrapper

Заключение

functools.wraps:

  • Сохраняет метаинформацию функции
  • Необходим для правильной работы help(), документации, IDE
  • Позволяет получить оригинальную функцию через wrapped
  • Должен быть ВСЕ декораторов (кроме специальных случаев)
  • Одна строка кода, которая спасает массу проблем
Для чего используется functools.wraps в декораторах? | PrepBro