Python Language
Декораторы
Поиск…
Вступление
Функции декоратора - это шаблоны проектирования программного обеспечения. Они динамически изменяют функциональные возможности функции, метода или класса без непосредственного использования подклассов или изменения исходного кода декорированной функции. При правильном использовании декораторы могут стать мощными инструментами в процессе разработки. В этом разделе рассматриваются реализации и применения функций декоратора в Python.
Синтаксис
def decorator_function (f): pass # определяет декоратор с именем decorator_function
@decorator_function
def decoration_function (): pass # функция теперь завернута (украшена) decorator_functiondecor_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()