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

Теряем ли доступ к исходной функции, если используем декоратор в 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. Документация в порядке

Заключение

Ответ на вопрос: Да, теряем, но можно сохранить:

  1. Используйте @functools.wraps — это стандарт Python
  2. Сохраняются метаданные: __name__, __doc__, __annotations__
  3. Доступ к оригиналу через func.__wrapped__
  4. Без @wraps информация теряется безвозвратно

Это простое правило, которое следует применять автоматически в каждом декораторе.

Теряем ли доступ к исходной функции, если используем декоратор в Python | PrepBro