Python Lab
← Все статьи

Декораторы Python: от простых к сложным

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

10 марта 2025 г.·7 мин чтения
декораторыfunctoolsООП

Что такое декоратор?

Декоратор — это функция, которая принимает другую функцию и возвращает новую, «обёрнутую» версию.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("До вызова")
        result = func(*args, **kwargs)
        print("После вызова")
        return result
    return wrapper

@my_decorator
def say_hello(name: str) -> str:
    return f"Привет, {name}!"

Символ @my_decorator — это синтаксический сахар для say_hello = my_decorator(say_hello).


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

Без functools.wraps обёртка «ворует» метаданные исходной функции:

print(say_hello.__name__)  # wrapper — неправильно!
print(say_hello.__doc__)   # None

Используйте @functools.wraps(func):

import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("До вызова")
        return func(*args, **kwargs)
    return wrapper

Теперь say_hello.__name__ вернёт "say_hello".


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

Если декоратор принимает аргументы, нужен ещё один уровень вложенности:

def repeat(n: int):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def greet():
    print("Привет!")

greet()  # Привет! Привет! Привет!

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

Таймер

import time

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

Кеширование (LRU Cache)

Python предоставляет встроенный декоратор @functools.lru_cache:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

Декоратор-класс

Декоратором может быть класс, реализующий __call__:

class Retry:
    def __init__(self, attempts: int = 3):
        self.attempts = attempts

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(self.attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if i == self.attempts - 1:
                        raise
        return wrapper

@Retry(attempts=5)
def flaky_request():
    ...

Порядок применения нескольких декораторов

Декораторы применяются снизу вверх:

@decorator_a
@decorator_b
def foo():
    pass

# Эквивалентно: decorator_a(decorator_b(foo))

Итог

ПаттернКогда использовать
@functools.wrapsВсегда — сохраняет метаданные
Декоратор с аргументамиКогда нужна конфигурация
@lru_cacheРекурсия / дорогие вычисления
Декоратор-классКогда нужно хранить состояние

Декораторы — мощный инструмент для разделения обязанностей: логирование, кеширование, ретраи и авторизация не должны загрязнять бизнес-логику.

Хочешь закрепить знания?

Попробуй решить задачи на Python в интерактивном тренажёре

К задачам