Python decorator

Декораторм в python является функция, возвращающая другую функцию. Обычно применяется как преобразование функции с использованием синтаксиса @wrapper. Распространенными примерами декораторов являются classmethod() и staticmethod().

Декоратор - это именованный вызываемый объект (лямбда-функции не допускаются), который принимает один объект при вызове (декорируемый объект) и возвращает другой объект. Вызываемым объектом может быть любой объект, реализующий метод __call__

Cледующие два определения декоратора семантически эквивалентны:

def f(...):
    ...
f = staticmethod(f)

@staticmethod
def f(...):
    ...

Примеры декораторов:

Функция декоратор принимает другую функцию, передает ее аргументы обертке и возвращает результат вычислений

>>> def deco(this):
...     def wrapper(*args, **kwargs):
...         print('wrap')
...         return this(*args, **kwargs)
...     return wrapper

>>> @deco
>>> def main(a, b=2):
...     print(f'in main {a} != {b}')

>>> main(1)
wrap
in main 1 != 2

Класс-декоратор реализует тот же механизм в методе __call__

>>> class This:
...     def __call__(self, this):
...         def wrapper(*args, **kwargs):
...             print('wrap')
...             return this(*args, **kwargs)
...         return wrapper

>>> @This()
>>> def main_cls(a, b=2):
...     print(f'in main {a} != {b}')

>>> main_cls(1)
wrap
in main 1 != 2

Декоратор класса принимает класс вмест офункцйии и реализует метод, декорирующий методы класса

>>> def cls_deco(cls):
...     def wrapper(*args, **kwargs):
...         print('wrap')
...         wrapper.instance = cls(*args, **kwargs)
...         return wrapper.instance
...     return wrapper

>>> @cls_deco
>>> class Main:
...     def __init__(self, a, b=2):
...         self.a = a
...         self.b = b

...     def what(self):
...         return f'in Main {self.a} != {self.b}'

>>> c = Main(1)
>>> print(c.what())
wrap
in main 1 != 2

Еще один вопрос - это параметризация декоратора. Для этого необходим еще один уровень упаковке. Ниже пример декоратора, который можно использовать для теста функций. Обратите внимание насколько сложным может быть вызлов без использования синтаксиса @

>>> def profile(column='time', how=3):
...     def parametrized(function):
...         def wrapper(*args, **kwargs):
...             print('wraper')
...             import tempfile
...             from cProfile import Profile
...             from pstats import Stats

...             s = tempfile.mktemp()

...             profiler = Profile()
...             profiler.runcall(function, *args, **kwargs)
...             profiler.dump_stats(s)
...
...             p = Stats(s)
...             print('-'*5, f'{function.__name__}() profile', '-'*5)
...             p.sort_stats(column).print_stats(how)
...         return wrapper
...     return parametrized

>>> import time
>>> def what():
...     for _ in range(100):
...         time.sleep(1)

>>> profile()(what)()

Через сахар декоратора это было бы так:

>>> import time

>>> @profile()
>>> def what():
...     for _ in range(100):
...         time.sleep(1)

Обратите внимание, что вызов @profile осуществляется с () - даже если мы не передаем аргументы это необходимо сделать, чтобы не получить исключение.

Пример: как сделать параметризированный декоратор с помощью [functools]

Подробнее о декорировании функций и декорирвоании классов

Смотри еще: