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

Может ли класс быть декоратором?

2.0 Middle🔥 121 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Может ли класс быть декоратором

Да, класс полностью может быть декоратором. В Python декоратор — это просто callable объект, который принимает другую функцию (или класс) и возвращает модифицированную версию. Класс это callable, поэтому он может быть декоратором.

Основной механизм

Для использования класса как декоратора нужно реализовать метод __call__.

class MyDecorator:
    def __init__(self, func):
        self.func = func
        self.call_count = 0
    
    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"Функция вызывается {self.call_count} раз")
        return self.func(*args, **kwargs)

@MyDecorator
def greet(name):
    return f"Hello, {name}!"

# greet теперь это объект класса MyDecorator
print(greet("Alice"))  # Функция вызывается 1 раз, Hello, Alice!
print(greet("Bob"))    # Функция вызывается 2 раз, Hello, Bob!
print(greet.call_count)  # 2

Что происходит:

@MyDecorator
def greet(name):
    pass

# Эквивалентно:
greet = MyDecorator(greet)
# greet теперь объект MyDecorator, а оригинальная функция хранится в self.func

Пример 1: Декоратор класса для подсчета вызовов

import functools
from typing import Any, Callable

class CallCounter:
    """Подсчитывает количество вызовов функции"""
    
    def __init__(self, func: Callable):
        self.func = func
        self.calls = 0
        functools.update_wrapper(self, func)  # Сохраняем метаданные
    
    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        self.calls += 1
        print(f"Вызов #{self.calls}")
        return self.func(*args, **kwargs)
    
    def reset(self):
        self.calls = 0

@CallCounter
def add(a: int, b: int) -> int:
    return a + b

print(add(2, 3))  # Вызов #1, 5
print(add(4, 5))  # Вызов #2, 9
print(f"Всего вызовов: {add.calls}")  # 2
add.reset()
print(f"После сброса: {add.calls}")  # 0

Пример 2: Кэширующий декоратор класса

import functools
from typing import Any, Callable

class CacheDecorator:
    """Кэширует результаты функции"""
    
    def __init__(self, func: Callable):
        self.func = func
        self.cache = {}
        functools.update_wrapper(self, func)
    
    def __call__(self, *args: Any) -> Any:
        # Только для hashable аргументов
        if args in self.cache:
            print(f"Возвращаю из кэша: {args}")
            return self.cache[args]
        
        result = self.func(*args)
        self.cache[args] = result
        return result
    
    def clear_cache(self):
        self.cache.clear()

@CacheDecorator
def fibonacci(n: int) -> int:
    """Вычисление чисел Фибоначчи с кэшем"""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(5))  # Вычисления
print(fibonacci(5))  # Из кэша
fibonacci.clear_cache()
print(fibonacci(5))  # Вычисления заново

Пример 3: Декоратор класса с параметрами

Для параметризации нужна дополнительная обертка.

class RateLimiter:
    """Ограничивает частоту вызовов функции"""
    
    def __init__(self, max_calls: int, interval: int):
        self.max_calls = max_calls
        self.interval = interval
        self.calls = []
    
    def __call__(self, func: Callable) -> Callable:
        # Возвращаем функцию-обертку
        def wrapper(*args, **kwargs):
            import time
            now = time.time()
            
            # Удаляем старые вызовы
            self.calls = [t for t in self.calls if now - t < self.interval]
            
            if len(self.calls) >= self.max_calls:
                raise Exception(f"Rate limit exceeded: {self.max_calls} calls per {self.interval}s")
            
            self.calls.append(now)
            return func(*args, **kwargs)
        
        return wrapper

# Использование с параметрами
@RateLimiter(max_calls=3, interval=1)
def api_call():
    print("API called")
    return "success"

api_call()  # success
api_call()  # success
api_call()  # success
# api_call()  # Ошибка: Rate limit exceeded

Пример 4: Декоратор класса с сохранением функциональности

import functools
from typing import Callable, Any

class ValidateDecorator:
    """Валидирует типы аргументов перед вызовом"""
    
    def __init__(self, func: Callable, expected_types: dict):
        self.func = func
        self.expected_types = expected_types
        functools.update_wrapper(self, func)
    
    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        # Валидация позиционных аргументов
        for i, arg in enumerate(args):
            if i in self.expected_types:
                expected_type = self.expected_types[i]
                if not isinstance(arg, expected_type):
                    raise TypeError(
                        f"Arg {i}: expected {expected_type}, got {type(arg)}"
                    )
        
        return self.func(*args, **kwargs)

def validate_args(expected_types: dict) -> Callable:
    """Обертка для параметризации"""
    def decorator(func: Callable) -> ValidateDecorator:
        return ValidateDecorator(func, expected_types)
    return decorator

@validate_args({0: int, 1: str})
def process(number: int, text: str) -> str:
    return f"Number: {number}, Text: {text}"

print(process(42, "hello"))  # OK
# process("42", "hello")  # TypeError

Пример 5: Декоратор для классов

Класс-декоратор может украшать другие классы.

class Singleton:
    """Класс-декоратор для реализации паттерна Singleton"""
    
    def __init__(self, cls: type):
        self.cls = cls
        self.instance = None
    
    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = self.cls(*args, **kwargs)
        return self.instance

@Singleton
class Database:
    def __init__(self):
        self.connection = "Connected to DB"

db1 = Database()
db2 = Database()

print(db1 is db2)  # True (один объект)
print(db1.connection)  # Connected to DB

Сравнение: класс-декоратор vs функция-декоратор

Функция-декоратор

def count_calls(func):
    def wrapper(*args, **kwargs):
        wrapper.calls += 1
        return func(*args, **kwargs)
    wrapper.calls = 0
    return wrapper

@count_calls
def my_func():
    pass

Класс-декоратор

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.calls = 0
    
    def __call__(self, *args, **kwargs):
        self.calls += 1
        return self.func(*args, **kwargs)

@CountCalls
def my_func():
    pass

Плюсы класса-декоратора

  1. Состояние — класс может хранить состояние (self.calls, self.cache)
  2. Методы — можно добавить методы (reset, clear_cache)
  3. Читаемость — код более структурирован
  4. Расширяемость — легче наследоваться
  5. Параметризация — легче добавлять параметры в init

Минусы класса-декоратора

  1. Больше кода — нужно писать init и call
  2. Память — каждый вызов создает объект
  3. Сложность — нужно помнить про call

Лучшие практики

import functools
from typing import Callable, TypeVar, Any

F = TypeVar('F', bound=Callable[..., Any])

class MyDecorator:
    def __init__(self, func: F) -> None:
        self.func = func
        functools.update_wrapper(self, func)  # Сохраняем метаданные
    
    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        # Логика декоратора
        return self.func(*args, **kwargs)

# Всегда используй functools.update_wrapper!

Вывод

Да, класс может быть декоратором. Просто реализуй __call__ и используй @ClassName. Класс-декоратор особенно полезен, когда нужно сохранять состояние или добавлять методы.

Может ли класс быть декоратором? | PrepBro