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

Что такое WRPAS в Python?

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

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

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

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

Что такое WRAPS в Python

Примечание: вопрос содержит опечатку — правильное название WRAPS (не WRPAS). @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):
    """Приветствует человека"""
    return f'Привет, {name}!'

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

Решение с @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'Привет, {name}!'

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

Что копирует @wraps

По умолчанию копируются:

WRAPPER_ASSIGNMENTS = (
    '__module__',      # Имя модуля
    '__name__',        # Имя функции
    '__qualname__',    # Полное квалифицированное имя
    '__annotations__', # Аннотации типов
    '__doc__',         # Docstring
)

WRAPPER_UPDATES = (
    '__dict__',        # Словарь атрибутов
)

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

Декоратор для логирования:

from functools import wraps
import logging

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f'Вызов {func.__name__} с args={args}, kwargs={kwargs}')
        result = func(*args, **kwargs)
        logging.info(f'{func.__name__} вернул {result}')
        return result
    return wrapper

@log_calls
def multiply(a: int, b: int) -> int:
    """Перемножает два числа"""
    return a * b

print(multiply.__name__)    # 'multiply' ✅
print(multiply.__doc__)     # 'Перемножает два числа' ✅
multiply(3, 4)  # Логируется с правильным именем

Декоратор для проверки типов:

from functools import wraps
from typing import get_type_hints

def check_types(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        hints = get_type_hints(func)
        # Проверяем типы аргументов
        # ...
        return func(*args, **kwargs)
    return wrapper

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

# get_type_hints(add) работает правильно благодаря @wraps
print(get_type_hints(add))  # {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}

Декоратор для кэширования:

from functools import wraps, lru_cache
import time

def cache_with_ttl(seconds=60):
    def decorator(func):
        cache = {}
        @wraps(func)  # Сохраняет __name__, __doc__, etc.
        def wrapper(*args):
            if args in cache:
                cached_result, timestamp = cache[args]
                if time.time() - timestamp < seconds:
                    return cached_result
            
            result = func(*args)
            cache[args] = (result, time.time())
            return result
        return wrapper
    return decorator

@cache_with_ttl(seconds=30)
def expensive_operation(x: int) -> int:
    """Дорогая операция, кэшируется на 30 сек"""
    time.sleep(2)
    return x ** 2

print(expensive_operation.__name__)  # 'expensive_operation' ✅
print(expensive_operation.__doc__)   # 'Дорогая операция...' ✅

Декоратор с параметрами и @wraps

from functools import wraps

def repeat(times: int):
    """Декоратор: повторяет функцию N раз"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat(times=3)
def greet(name: str) -> str:
    """Приветствует человека"""
    return f'Привет, {name}!'

print(greet.__name__)  # 'greet' ✅
print(greet('Алиса'))  # ['Привет, Алиса!', 'Привет, Алиса!', 'Привет, Алиса!']

Важно для документирования

Без @wraps — документация теряется:

def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def my_function():
    """Важная функция"""
    pass

help(my_function)  # Выведет документ wrapper'а, не my_function!

С @wraps — документация сохраняется:

from functools import wraps

def good_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def my_function():
    """Важная функция"""
    pass

help(my_function)  # Выведет оригинальную документацию my_function!

Использование с async функциями

from functools import wraps
import asyncio

def async_timer(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        start = asyncio.get_event_loop().time()
        result = await func(*args, **kwargs)
        end = asyncio.get_event_loop().time()
        print(f'{func.__name__} заняла {end - start:.2f}сек')
        return result
    return wrapper

@async_timer
async def fetch_data(url: str) -> str:
    """Получает данные с сервера"""
    await asyncio.sleep(1)
    return 'data'

print(fetch_data.__name__)  # 'fetch_data' ✅

Проверка работы help() и inspect

from functools import wraps
import inspect

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def process(data: list) -> dict:
    """Обрабатывает данные"""
    return {'status': 'ok'}

# Всё работает правильно
print(inspect.signature(process))  # (data: list) -> dict ✅
print(inspect.getsource(process))  # Выведет оригинальную функцию ✅
help(process)                      # Покажет правильный docstring ✅

Почему @wraps важна

1. Отладка: при ошибке видишь правильное имя функции 2. Документирование: help() и IDE подсказки работают корректно 3. Type checking: mypy видит правильные типы 4. Introspection: inspect модуль работает с оригинальной функцией 5. Testing: мок-объекты корректно работают с декорированными функциями

Best Practice

ВСЕГДА используй @wraps при создании декоратора:

from functools import wraps

def my_decorator(func):
    @wraps(func)  # ОБЯЗАТЕЛЬНО!
    def wrapper(*args, **kwargs):
        # твой код здесь
        return func(*args, **kwargs)
    return wrapper

@wraps — это маленький, но очень важный инструмент, который делает декораторы более прозрачными и совместимыми с инструментами Python для отладки и документирования.