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

Как уже упоминалось во введении, декоратор - это функция, которую можно применять к другой функции для улучшения ее поведения. Синтаксический сахар эквивалентно следующему: my_func = decorator(my_func) . Но что , если decorator был вместо класса? Синтаксис будет по- прежнему работать, за исключением того, что теперь my_func заменяется экземпляром decorator класса. Если этот класс реализует __call__() метод магии, то он все равно будет возможно использовать my_func , как если бы это была функция:

 class Decorator(object):
    """Simple decorator class."""

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('Before the function call.')
        res = self.func(*args, **kwargs)
        print('After the function call.')
        return res

@Decorator
def testfunc():
    print('Inside the function.')

testfunc()
# Before the function call.
# Inside the function.
# After the function call.

 

Обратите внимание, что функция, украшенная декоратором класса, больше не будет считаться «функцией» с точки зрения проверки типов:

 import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>

 

Методы украшения

Для методов декорирования необходимо определить дополнительный __get__ -метод:

 from types import MethodType

class Decorator(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('Inside the decorator.')
        return self.func(*args, **kwargs)

    def __get__(self, instance, cls):
        # Return a Method if it is called on an instance
        return self if instance is None else MethodType(self, instance)

class Test(object):
    @Decorator
    def __init__(self):
        pass

a = Test()
 

Внутри декоратор.

Предупреждение!

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

 from types import MethodType

class CountCallsDecorator(object):
    def __init__(self, func):
        self.func = func
        self.ncalls = 0    # Number of calls of this method

    def __call__(self, *args, **kwargs):
        self.ncalls += 1   # Increment the calls counter
        return self.func(*args, **kwargs)

    def __get__(self, instance, cls):
        return self if instance is None else MethodType(self, instance)

class Test(object):
    def __init__(self):
        pass

    @CountCallsDecorator
    def do_something(self):
        return 'something was done'

a = Test()
a.do_something()
a.do_something.ncalls   # 1
b = Test()
b.do_something()
b.do_something.ncalls   # 2