Введение

Примеры

Функция декоратора

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

 # This simplest decorator does nothing to the function being decorated. Such
# minimal decorators can occasionally be used as a kind of code markers.
def super_secret_function(f):
    return f

@super_secret_function
def my_function():
    print("This is my secret function.")

 

@ -Notation является синтаксический сахар , который эквивалентен следующему:

 my_function = super_secret_function(my_function)

 

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

 def disabled(f):
    """
    This function returns nothing, and hence removes the decorated function
    from the local scope.
    """
    pass

@disabled
def my_function():
    print("This function can no longer be called...")

my_function()
# TypeError: 'NoneType' object is not callable

 

Таким образом, мы обычно определяем новую функцию внутри декоратора и вернуть его. Эта новая функция сначала должна выполнить то, что ей нужно, затем вызвать исходную функцию и, наконец, обработать возвращаемое значение. Рассмотрим эту простую функцию декоратора, которая печатает аргументы, которые получает исходная функция, а затем вызывает ее.

 #This is the decorator
def print_args(func):
    def inner_func(*args, **kwargs):
        print(args)
        print(kwargs)
        return func(*args, **kwargs) #Call the original function with its arguments.
    return inner_func

@print_args
def multiply(num_a, num_b):
    return num_a * num_b

print(multiply(3, 5))
#Output:
# (3,5) - This is actually the 'args' that the function receives.
# {} - This is the 'kwargs', empty because we didn't specify keyword arguments.
# 15 - The result of the function. 

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

Как уже упоминалось во введении, декоратор - это функция, которую можно применять к другой функции для улучшения ее поведения. Синтаксический сахар эквивалентно следующему: 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 

Создание декоратора, похожего на декорированную функцию

Декораторы обычно удаляют метаданные функции, поскольку они не совпадают. Это может вызвать проблемы при использовании метапрограммирования для динамического доступа к метаданным функции. Метаданные также включают строки документации функции и ее имя. functools.wraps делает декорированный функцию выглядеть исходной функции путем копирования нескольких атрибутов функции - оболочки.

 from functools import wraps

 

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

Как функция

 def decorator(func):
    # Copies the docstring, name, annotations and module to the decorator
    @wraps(func)
    def wrapped_func(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped_func

@decorator
def test():
    pass

test.__name__
 

'тестовое задание'

Как класс

 class Decorator(object):
    def __init__(self, func):
        # Copies name, module, annotations and docstring to the instance.
        self._wrapped = wraps(func)(self)

    def __call__(self, *args, **kwargs):
        return self._wrapped(*args, **kwargs)

@Decorator
def test():
    """Docstring of test."""
    pass

test.__doc__
 

«Строка теста».

Декоратор с аргументами (декоратор фабрики)

Декоратор принимает только один аргумент: функцию, которую нужно декорировать. Нет возможности передать другие аргументы.

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

Функции декоратора

 def decoratorfactory(message):
    def decorator(func):
        def wrapped_func(*args, **kwargs):
            print('The decorator wants to tell you: {}'.format(message))
            return func(*args, **kwargs)
        return wrapped_func
    return decorator

@decoratorfactory('Hello World')
def test():
    pass

test()

 

Декоратор хочет сказать вам: Hello World

Важная заметка:

С такой декоратор фабрикой вы должны вызвать декоратор с парой скобок:

 @decoratorfactory # Without parentheses
def test():
    pass

test()
 

TypeError: decorator () отсутствует 1 обязательный позиционный аргумент: 'func'

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

 def decoratorfactory(*decorator_args, **decorator_kwargs):

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

        def __call__(self, *args, **kwargs):
            print('Inside the decorator with arguments {}'.format(decorator_args))
            return self.func(*args, **kwargs)

    return Decorator

@decoratorfactory(10)
def test():
    pass

test()

 

Внутри декоратор с аргументами (10,)

Создать синглтон-класс с декоратором

Синглтон - это шаблон, который ограничивает создание экземпляра класса одним экземпляром / объектом. Используя декоратор, мы можем определить класс как одноэлементный, заставив класс либо вернуть существующий экземпляр класса, либо создать новый экземпляр (если он не существует).

 def singleton(cls):    
    instance = [None]
    def wrapper(*args, **kwargs):
        if instance[0] is None:
            instance[0] = cls(*args, **kwargs)
        return instance[0]

    return wrapper

 

Этот декоратор может быть добавлен в любое объявление класса и будет обеспечивать создание не более одного экземпляра класса. Любые последующие вызовы вернут уже существующий экземпляр класса.

 @singleton
class SomeSingletonClass:
    x = 2
    def __init__(self):
        print("Created!")

instance = SomeSingletonClass()  # prints: Created!
instance = SomeSingletonClass()  # doesn't print anything
print(instance.x)                # 2

instance.x = 3
print(SomeSingletonClass().x)    # 3

 

Поэтому не имеет значения, обращаетесь ли вы к экземпляру класса через локальную переменную или создаете другой «экземпляр», вы всегда получаете один и тот же объект.

Использование декоратора для определения времени функции

 import time
def timer(func):
    def inner(*args, **kwargs):
        t1 = time.time()
        f = func(*args, **kwargs)
        t2 = time.time()
        print 'Runtime took {0} seconds'.format(t2-t1)
        return f
    return inner

@timer
def example_function():
    #do stuff


example_function() 

Синтаксис

Параметры

Примечания