← Назад к вопросам
Может ли класс быть декоратором?
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
Плюсы класса-декоратора
- Состояние — класс может хранить состояние (self.calls, self.cache)
- Методы — можно добавить методы (reset, clear_cache)
- Читаемость — код более структурирован
- Расширяемость — легче наследоваться
- Параметризация — легче добавлять параметры в init
Минусы класса-декоратора
- Больше кода — нужно писать init и call
- Память — каждый вызов создает объект
- Сложность — нужно помнить про 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. Класс-декоратор особенно полезен, когда нужно сохранять состояние или добавлять методы.