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]
- Подробнее о декорировании функций и декорирвоании классов
- Using a coroutine as decorator
- Decorators with parameters?
- Python decorators in classes
- How to strip decorators from a function in Python, undecorated
Смотри еще: