Шаг 163.
Python: сборник рецептов.
Метапрограммирование. Определение декораторов как части класса

    На этом шаге мы рассмотрим особенности определения таких декораторов.

Задача

    Вы хотите определить декоратор внутри определения класса и применить его к другим функциям или методам.

Решение

    Определение декоратора внутри класса выполняется как обычно, но сначала вы должны выработать способ, которым декоратор будет применяться. В частности, будет ли он применяться как метод экземпляра или как метод класса. Вот пример, который иллюстрирует разницу:

>>> 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 еще не был создан.

    На следующем шаге мы рассмотрим определение декораторов как классов.




Предыдущий шаг Содержание Следующий шаг