Класс как декоратор (call)

Класс как декоратор (call) Декораторы

Привет! Мы уже научились создавать декораторы с помощью функций. Но в Python есть и другой способ — использовать классы. Это даёт больше гибкости, особенно когда нужно хранить состояние.

В этой статье мы разберём:

  • Магический метод __call__
  • Класс как декоратор
  • Преимущества классовых декораторов
  • Практические примеры

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный Python
  • Базовое понимание классов и декораторов

Совет: Декораторы на классах — это более мощный способ, чем функциональные декораторы, особенно для сложной логики.

Основная часть

Магический метод call

Метод __call__ позволяет сделать экземпляр класса вызываемым, как функцию.

class CallableClass:
    def __call__(self, x):
        print(f"Вызов с аргументом: {x}")

obj = CallableClass()
obj(10)   # Вызов с аргументом: 10

Совет: __call__ — это то, что делает объект «вызываемым» (callable).

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

Чтобы использовать класс как декоратор, нужно:

  1. Принять функцию в __init__
  2. Сохранить её
  3. Реализовать __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__
  • 📝 Написали классовые декораторы
  • 🔍 Разобрали преимущества перед функциональными
  • 💡 Практические примеры

КВИЗ

Что дальше?

Оцените статью
IMI-DS - PYTHON LERNEN
Содержание
Оглавление ×