Типичный способ поддержания какого-то состояния, это использование классов. Здесь мы перепишем пример декоратора @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