Декораторы в Python
Table of Contents
1 Декораторы в Python
1.1 Декораторы и Паттерн Декоратор
Возможно использовать декораторы в Python чтобы реализовать паттерн декоратор, но это крайне редкое их использование. Декораторы в Python больше всего похожи на макросы.
1.2 Декораторы функций
class myDecorator(object): def __init__(self, f): print("inside my_decorator.__init__()") f() def __call__(self): print("inside my_decorator.__call__()") @myDecorator def aFunction(): print("inside aFunction") print("Finished decorating aFunction()") aFunction()
inside my_decorator.__init__() inside aFunction Finished decorating aFunction() inside my_decorator.__call__()
1.3 Изменение имени функции, возвращённой из декоратора
Если мы используем декоратор-функцию, то возвращённая им функция будет иметь совсем другое название, для решения это проблемы мы можем переопределить аттрибут __name__
def entry_exit(f): def wrapper(): print("Entering", f.__name__) f() print("Exited", f.__name__) wrapper.__name__ = f.__name__ return wrapper @entry_exit def original_name(): print("inside original name") original_name()
Entering original_name inside original name Exited original_name
1.4 Декораторы с аргументами
class decorator_with_arguments(object): def __init__(self, arg1, arg2, arg3): """ If there are decorator arguments, the function to be decorated is not passed to the constructor! """ print("Inside __init__()") self.arg1 = arg1 self.arg2 = arg2 self.arg3 = arg3 def __call__(self, f): """ If there are decorator arguments, __call__() is only called once, as part of the decoration process! You can only give it a single argument, which is the function object. """ print("Inside __call__()") def wrapper(*args): print("Inside wrapper()") print("Decorator arguments:", self.arg1, self.arg2, self.arg3) f(*args) print("After f(*args)") return wrapper @decorator_with_arguments("hello", "world", 42) def sayHello(a1, a2, a3, a4): print("sayHello arguments: ", a1, a2, a3, a4) print("After decoration") print("Preparing to call sayHello()") sayHello("say", "hello", "argument", "list") print("after first sayHello() call") sayHello("a", "different", "set of", "arguments") print("after second sayHello() call")
Inside __init__() Inside __call__() After decoration Preparing to call sayHello() Inside wrapper() Decorator arguments: hello world 42 sayHello arguments: say hello argument list After f(*args) after first sayHello() call Inside wrapper() Decorator arguments: hello world 42 sayHello arguments: a different set of arguments After f(*args) after second sayHello() call
1.5 Модуль decorator
Так как написание корректного декоратора требует значительного опыта и не так уж просто как кажется, то был создан модуль decorator.
1.5.1 Определение декоратора
Декораторы могут быть двух видов:
- signature-preserving декораторы, т.е., вызываемые объекты, принимающие функцию на фход и возвращающие функцию на выход с сохранением сигнатуры
- signature-changing декораторы, т.е, декораторы, которые изменяют сигнатуру входной функции или декораторы, возвращающие non-callable объект К signature-changing декораторам относятся, например, декораторы @staticmethod и @classmethod, т.к. они принимают на вход функцию и возвращают объект дескриптора, который не является ни функцией, ни вызываемым объектом.
1.5.2 Описание проблемы
Предположим, что мы хотим трассировать выполнение функции:
try: from functools import update_wrapper except ImportError: # using Python version < 2.5 def decorator_trace(f): def newf(*args, **kw): print "calling %s with args %s, %s" % (f.__name__, args, kw) return f(*args, **kw) newf.__name__ = f.__name__ newf.__dict__.update(f.__dict__) newf.__doc__ = f.__doc__ newf.__module__ = f.__module__ return newf else: # using Python 2.5+ def decorator_trace(f): def newf(*args, **kw): print "calling %s with args %s, %s" % (f.__name__, args, kw) return f(*args, **kw) return update_wrapper(newf, f)
1.5.3 Решение проблемы
Решение проблемы с постояным отслеживанием чтобы все аттрибуты функции под декоратором остались прежними (имя, документация) заключается в использовании фабрики генераторов, которая спрячет всю сложность создания signature-preserving декораторов. Вот как мы можем реализовать функцию decorator_trace:
# Во-первых, импортируем модуль decorator from decorator import decorator # Затем объявляем вспомогательную функцию с сигнатурой (f, *args, **kwargs), # которая вызывает оригинальную функцию f с аргументами args и kwargs # и реализует возможность трассировки def trace(f, *args, **kwargs): print("calling %s with args %s, %s" % (f.__name__, args, kwargs)) return f(*args, **kwargs) # decorator может конвертировать вспомогательную функцию в signature-preserving объект-декоратор # т.е., это вызываемый объект, принимающий на вход функцию и возвращающий обёрнутую # в декоратор функцию с такой же точно сигнатурой, как и у оригинальной функции. @decorator(trace) def f1(x): pass f1(0)
calling f1 with args (0,), {}