Декораторы

Введение

Примеры

  • 37

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

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

     # 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. 
  • 23

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

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

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

    Декораторы обычно удаляют метаданные функции, поскольку они не совпадают. Это может вызвать проблемы при использовании метапрограммирования для динамического доступа к метаданным функции. Метаданные также включают строки документации функции и ее имя. 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__
     

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

  • 16

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

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

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

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

     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,)

  • 4

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

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

     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
    
     

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

  • 8

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

     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() 

Синтаксис

Параметры

Примечания