Привет! Мы уже научились создавать декораторы с помощью функций. Но в Python есть и другой способ — использовать классы. Это даёт больше гибкости, особенно когда нужно хранить состояние.
В этой статье мы разберём:
- Магический метод
__call__ - Класс как декоратор
- Преимущества классовых декораторов
- Практические примеры
- Что нужно знать перед началом
- Основная часть
- Магический метод call
- Класс как декоратор
- Декоратор с аргументами (класс)
- Хранение состояния
- Декоратор с параметрами и состоянием
- Сравнение с функциональными декораторами
- Задачи для закрепления
- Нюансы и подводные камни
- Сохранение метаданных
- call vs init
- Наследование
- 5. Частые ошибки и как их избежать
- Ошибка 1: Забыл call
- Ошибка 2: Неправильный порядок аргументов
- Ошибка 3: Потеря метаданных
- Шпаргалка
- Заключение
- КВИЗ
- Что дальше?
Что нужно знать перед началом
Для этого урока тебе понадобится:
- Установленный Python
- Базовое понимание классов и декораторов
Совет: Декораторы на классах — это более мощный способ, чем функциональные декораторы, особенно для сложной логики.
Основная часть
Магический метод call
Метод __call__ позволяет сделать экземпляр класса вызываемым, как функцию.
class CallableClass:
def __call__(self, x):
print(f"Вызов с аргументом: {x}")
obj = CallableClass()
obj(10) # Вызов с аргументом: 10Совет:
__call__— это то, что делает объект «вызываемым» (callable).
Класс как декоратор
Чтобы использовать класс как декоратор, нужно:
- Принять функцию в
__init__ - Сохранить её
- Реализовать
__call__для вызова функции
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"Вызов функции: {self.func.__name__}")
result = self.func(*args, **kwargs)
print(f"Результат: {result}")
return result
@Logger
def add(a, b):
return a + b
add(3, 5)
# Вызов функции: add
# Результат: 8Декоратор с аргументами (класс)
class Repeat:
def __init__(self, times):
self.times = times
def __call__(self, func):
def wrapper(*args, **kwargs):
for _ in range(self.times):
func(*args, **kwargs)
return wrapper
@Repeat(times=3)
def say_hello():
print("Привет!")
say_hello()
# Привет!
# Привет!
# Привет!Хранение состояния
Главное преимущество классовых декораторов — возможность хранить состояние.
class Counter:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Функция вызвана {self.count} раз")
return self.func(*args, **kwargs)
@Counter
def greet(name):
print(f"Привет, {name}!")
greet("Анна")
greet("Иван")
greet("Петр")
# Функция вызвана 1 раз
# Функция вызвана 2 раз
# Функция вызвана 3 разСовет: Состояние хранится в атрибутах класса и доступно между вызовами.
Декоратор с параметрами и состоянием
class Timer:
def __init__(self, name=None):
self.name = name or "Функция"
def __call__(self, func):
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{self.name} выполнилась за {end - start:.4f} сек")
return result
return wrapper
@Timer(name="Сумма")
def slow_add(a, b):
import time
time.sleep(0.5)
return a + b
slow_add(3, 5) # Сумма выполнилась за 0.5001 секСравнение с функциональными декораторами
| Классовый декоратор | Функциональный декоратор |
|---|---|
| Хранит состояние | Не хранит состояние |
| Более гибкий | Проще |
| Читаемее для сложной логики | Читаемее для простой логики |
| Поддерживает наследование | Не поддерживает |
Задачи для закрепления
Задача 1. Напиши классовый декоратор Logger, который выводит аргументы функции.
Задача 2. Напиши классовый декоратор Counter, который считает количество вызовов.
Задача 3. Напиши классовый декоратор Timer, который замеряет время выполнения.
Задача 4. Напиши классовый декоратор Repeat, который повторяет функцию n раз.
Задача 5. В чём преимущество классового декоратора перед функциональным?
Ответы:
Задача 1.
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"Аргументы: {args}, {kwargs}")
return self.func(*args, **kwargs)Задача 2.
class Counter:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Вызовов: {self.count}")
return self.func(*args, **kwargs)Задача 3.
class Timer:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
import time
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
print(f"Время: {end - start:.4f} сек")
return resultЗадача 4.
class Repeat:
def __init__(self, times):
self.times = times
def __call__(self, func):
def wrapper(*args, **kwargs):
for _ in range(self.times):
func(*args, **kwargs)
return wrapperЗадача 5.
# Состояние, гибкость, читаемость для сложной логикиНюансы и подводные камни
Сохранение метаданных
Классовый декоратор может потерять метаданные функции. Используй functools.wraps.
from functools import wraps
class Logger:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)call vs init
__init__— вызывается при создании экземпляра__call__— вызывается при вызове экземпляра как функции
Наследование
Классовые декораторы можно наследовать и расширять.
5. Частые ошибки и как их избежать
Ошибка 1: Забыл call
Всегда реализуй __call__, чтобы экземпляр был вызываемым.
Ошибка 2: Неправильный порядок аргументов
__init__ принимает функцию, __call__ — аргументы вызова.
Ошибка 3: Потеря метаданных
Используй functools.wraps.
Шпаргалка
| Что нужно | Как пишется |
|---|---|
| Класс как декоратор | class Decorator: def __init__(self, func): ... def __call__(self, *args, **kwargs): ... |
| Декоратор с аргументами | class Decorator: def __init__(self, arg): ... def __call__(self, func): ... |
| Сохранение состояния | self.state = value |
Заключение
Сегодня мы:
- 🧠 Узнали о методе
__call__ - 📝 Написали классовые декораторы
- 🔍 Разобрали преимущества перед функциональными
- 💡 Практические примеры








