На этом шаге мы рассмотрим особенности определения таких декораторов.
Вы хотите определить декоратор внутри определения класса и применить его к другим функциям или методам.
Определение декоратора внутри класса выполняется как обычно, но сначала вы должны выработать способ, которым декоратор будет применяться. В частности, будет ли он применяться как метод экземпляра или как метод класса. Вот пример, который иллюстрирует разницу:
>>> from functools import wraps >>> class A: # Декоратор как метод экземпляра def decorator1(self, func): @wraps(func) def wrapper(*args, **kwargs): print('Decorator 1') return func(*args, **kwargs) return wrapper # Декоратор как метод класса @classmethod def decorator2(cls, func): @wraps(func) def wrapper(*args, **kwargs): print('Decorator 2') return func(*args, **kwargs) return wrapper >>>
Вот пример того, как эти два декоратора будут применяться:
>>> # Как метод экземпляра >>> a = A() >>> @a.decorator1 def spam(): pass >>> # Как метод класса >>> @A.decorator2 def grok(): pass >>>
Если вы посмотрите внимательно, то заметите, как один из них применяется из экземпляра а, а другой - из класса A.
Определение декораторов в классе на первый вгляд может показаться странным, но примеры такого подхода вы встретите даже в стандартной библиотеке. В частности, встроенный декоратор @property на самом деле является классом с методами getter(), setter() и deleter(), каждый из которых действует как декоратор. Например:
>>> class Person: first_name = property() @first_name.getter def first_name(self): return self._first_name @first_name.setter def first_name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._first_name = value >>>
Главная причина использования такой реализации в том, что разнообразные методы-декораторы управляют состоянием связанного экземпляра property. Так что если у вас когда-либо возникнет задача, связанная с необходимостью создать декораторы, которые будут записывать или комбинировать данные "за кулисами", то это будет вполне разумным подходом.
Распространенная ошибка при написании декораторов в классах - путаница с использованием дополнительных аргументов self или cls в коде самого декоратора. Хотя самая внешняя функция-декоратор, такая как decorator1() или decorator2(), нуждается в аргументе self или cls (поскольку функции являются частью класса), но функция-обертка, создаваемая внутри, в общем случае не нуждается в дополнительном аргументе. Вот почему функция wrapper(), создаваемая в обоих декораторах, не включает аргумент self. Этот аргумент может понадобиться только в ситуации, когда вам нужен доступ к частям экземпляра в обертке. В противном случае вам не стоит волноваться по этому поводу.
И последний тонкий момент определения декораторов в классе: использование при наследовании. Предположим, например, что вы хотите применить один из декораторов, определенных в классе A, к методам, определенным в подклассе B. Чтобы сделать это, вам нужно написать такой код:
class B(A): @A.decorator2 def bar(self): pass
Рассматриваемый декоратор должен быть определен как метод класса, и вы должны явно использовать имя родительского класса A при применении. Вы не можете использовать имя типа @B.decorator2, потому что во время определения метода класс B еще не был создан.
На следующем шаге мы рассмотрим определение декораторов как классов.