Python LanguageДекораторы

Вступление

Функции декоратора - это шаблоны проектирования программного обеспечения. Они динамически изменяют функциональные возможности функции, метода или класса без непосредственного использования подклассов или изменения исходного кода декорированной функции. При правильном использовании декораторы могут стать мощными инструментами в процессе разработки. В этом разделе рассматриваются реализации и применения функций декоратора в Python.

Синтаксис

  • def decorator_function (f): pass # определяет декоратор с именем decorator_function

  • @decorator_function
    def decoration_function (): pass # функция теперь завернута (украшена) decorator_function

  • decor_function = decorator_function ( @decorator_function ) # это эквивалентно использованию синтаксического сахара @decorator_function

параметры

параметр подробности
е Функция, которая будет украшена (завернута)

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

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

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

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

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

Внутри декоратора.

Предупреждение!

Class Decorators производят только один экземпляр для определенной функции, поэтому для украшения метода с помощью декоратора класса будет использоваться один и тот же декоратор между всеми экземплярами этого класса:

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

Создание декоратора выглядит как украшенная функция

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

Создать одноэлементный класс с декоратором

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

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