← Назад к вопросам
Для чего используется 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
- Должен быть ВСЕ декораторов (кроме специальных случаев)
- Одна строка кода, которая спасает массу проблем