← Назад к вопросам
Теряем ли доступ к исходной функции, если используем декоратор в Python
2.0 Middle🔥 191 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Декораторы и доступ к исходной функции
Быстрый ответ
Да, обычно теряем доступ к исходной функции при использовании декоратора, но есть способы сохранить доступ. Это важный момент, который часто упускают при разработке.
Проблема: потеря информации о функции
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"Вызов функции")
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet(name):
"""Приветствует пользователя"""
return f"Hello, {name}!"
# Проблема: исходная функция потеряна
print(greet.__name__) # Выведет: 'wrapper'
print(greet.__doc__) # Выведет: None
print(greet.__module__) # Может быть неправильным
# Исходная функция недоступна
original = None # Как её получить?
Решение: functools.wraps
Правильный способ — использовать @functools.wraps:
from functools import wraps
def my_decorator(func):
@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.__module__) # '__main__'
print(greet.__annotations__) # {'name': str, 'return': str}
Что копирует functools.wraps
from functools import WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
print(WRAPPER_ASSIGNMENTS) # Копирует метаданные
print(WRAPPER_UPDATES) # Обновляет словарь
# Это означает, что копируются:
# - __module__ — модуль функции
# - __name__ — имя функции
# - __qualname__ — полное имя
# - __annotations__ — подсказки типов
# - __doc__ — docstring
Доступ к оригинальной функции через wrapped
from functools import wraps
def debug_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Debug: вызов {func.__name__}")
return func(*args, **kwargs)
return wrapper
@debug_decorator
def calculate(x, y):
"""Сложение двух чисел"""
return x + y
# functools.wraps устанавливает __wrapped__
original_func = calculate.__wrapped__
print(original_func(2, 3)) # 5 — исходная функция работает
Практический пример: кэширование
from functools import wraps
def cache_decorator(func):
cache = {}
@wraps(func)
def wrapper(n):
if n in cache:
return cache[n]
result = func(n)
cache[n] = result
return result
wrapper.cache = cache # Добавляем доступ к кэшу
wrapper.clear_cache = lambda: cache.clear()
return wrapper
@cache_decorator
def fibonacci(n):
"""Вычисляет n-е число Фибоначчи"""
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 55
print(fibonacci.cache) # Кэш функции
# Доступ к оригинальной функции
original = fibonacci.__wrapped__
print(original(5)) # Работает без кэша
Сложные декораторы с параметрами
from functools import wraps
def retry(max_attempts: int = 3, delay: float = 1.0):
"""Декоратор с параметрами"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
import time
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unreliable_operation():
import random
if random.random() < 0.7:
raise Exception("Сбой!")
return "Успех!"
Когда мы ДЕЙСТВИТЕЛЬНО теряем доступ
def bad_decorator(func):
def wrapper(*args, **kwargs):
print("Before")
return func(*args, **kwargs)
# Забыли @wraps — потеря метаданных
return wrapper
@bad_decorator
def greet(name):
return f"Hello, {name}!"
print(greet.__name__) # 'wrapper' — потеря инфо
print(hasattr(greet, '__wrapped__')) # False
# Исходная функция потеряна безвозвратно
Множественные декораторы
from functools import wraps
def decorator_a(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("A before")
result = func(*args, **kwargs)
print("A after")
return result
return wrapper
def decorator_b(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("B before")
result = func(*args, **kwargs)
print("B after")
return result
return wrapper
@decorator_a
@decorator_b
def my_function():
print("Function")
# Доступ к исходной через цепь __wrapped__
original = my_function.__wrapped__.__wrapped__
Лучшие практики
from functools import wraps
from typing import Callable, TypeVar, Any
F = TypeVar('F', bound=Callable[..., Any])
def well_designed_decorator(func: F) -> F:
"""Хороший декоратор с сохранением типов"""
@wraps(func)
def wrapper(*args, **kwargs):
# Логика декоратора
return func(*args, **kwargs)
return wrapper
# Преимущества:
# 1. Метаданные функции сохранены
# 2. IDE понимает типы
# 3. Доступ к исходной через __wrapped__
# 4. Документация в порядке
Заключение
Ответ на вопрос: Да, теряем, но можно сохранить:
- Используйте @functools.wraps — это стандарт Python
- Сохраняются метаданные:
__name__,__doc__,__annotations__ - Доступ к оригиналу через
func.__wrapped__ - Без @wraps информация теряется безвозвратно
Это простое правило, которое следует применять автоматически в каждом декораторе.