Сообщить об ошибке.

Использование класса в качестве декоратора Python

Типичный способ поддержания какого-то состояния, это использование классов. Здесь мы перепишем пример декоратора @counter из материала "Шаблон декоратора общего назначения", используя класс в качестве декоратора.

Как мы уже знаем, что синтаксис декоратора - это более простой способ написания выражения func = decorator(func). Поэтому, если decorator является классом, он должен принимать функцию func в качестве аргумента в своем методе __init__(). Кроме того, класс должен быть вызываемым, чтобы он мог выступать в качестве декоратора функции.

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

class Counter:
    def __init__(self, start=0):
        self.count = start

    def __call__(self):
        self.count += 1
        print(f'Всего вызовов {self.count}')

Метод __call__() выполняется каждый раз, когда вы пытаетесь вызвать экземпляр класса:

>>> counter = Counter()
>>> counter()
# Всего вызовов 1
>>> counter()
# Всего вызовов 2
>>> counter.count
# 2

Типичная реализация класса декоратора должна быть выполнена с методами __init__() и __call__():

import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.num = 0

    def __call__(self, *args, **kwargs):
        self.num += 1
        print(f'Вызов {self.func.__name__!r}: {self.num}')
        return self.func(*args, **kwargs)

@CountCalls
def say(name):
    print(f'Привет {name}')

Обратите внимание, что для предотвращения изменения атрибутов оригинальной функции нужно использовать функцию update_wrapper() модуля functools, вместо декоратора @functools.wraps.

Метод __init__() должен хранить ссылку на функцию и может выполнять любую другую необходимую инициализацию. Метод __call__() будет вызываться вместо декорированной функции. Он делает по существу то же самое, что и функция wrapper() в декораторе общего назначения.

>>> say('Андрей')
# Вызов 'say': 1
# Привет Андрей
>>> say('Андрей')
# Вызов 'say': 2
# Привет Андрей
>>> say('Андрей')
# Вызов 'say': 3
# Привет Андрей
>>> say.num
# 3