Декораторы - это такая конструкция языка, которая может изменить поведение функции, метода, класса и т.д. не изменяя ее саму.
Это идеальный способ что то изменить в программе без модификации кода изменяемого объекта.
Разберем процесс декорирования функции.
Для начала, рассмотрим некоторые особенности функций.
def greet(name):
return "hello "+name
greet_someone = greet
print greet_someone("John")
# Outputs: hello John
Как видно, функция может быть присвоена переменной как обычное значение и следовательно быть передана как параметр другой функции.
def greet(name):
def get_message():
return "Hello %s" % name
result = get_message()+'!'
return result
print greet("John")
# Outputs: Hello John
При определении внутренней функции все переменные, переданные во внешнюю, буду сохранены во внутреннем пространстве (замыкании) внешней функции и доступны из внутренней.
def greet(name):
return "Hello " + name
def call_func(func):
other_name = "John"
return func(other_name)
print call_func(greet)
# Outputs: Hello John
Функция может быть передана параметром и быть вызвана изнутри той функции, в которую была передана.
Иными словами - функция может создавать функцию.
def compose_greet_func():
def get_message():
return "Hello there!"
return get_message
greet = compose_greet_func()
print greet()
# Outputs: Hello there!
def compose_greet_func(name):
def get_message():
return "Hello there "+name+"!"
return get_message
greet = compose_greet_func("John")
print greet()
# Outputs: Hello there John!
Обратите внимание на то как мы забираем переменную name из замыкания и возвращаем внутреннюю функцию.
В момент вызова greet = compose_greet_func(“John”) в greet будет находится строка “John”.
Давайте совместим идеи, изложенные выше и построим декоратор.
Putting the ideas mentioned above together, we can build a decorator. In this example let’s consider a function that wraps the string output of another function by p tags.
В примере предположим, что нам необходимо изменить вывод функции get_text, заключив ее вывод в тэги
.def get_text(name):
return "Hello, {0}".format(name)
def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper
my_get_text = p_decorate(get_text)
print my_get_text("John")
# <p>Hello, John</p>
Это и был наш первый декоратор.
Т.е. функция-декоратор, принимающаю другую (декорируемую) функцию в качестве аргумента и изменяющая ее поведение.
Поведение изменяется внутри вложенной функции декоратора строкой return “ {0}
Применение декоратора может быть упрощено использованием знака @.
Этот знак - эквивалент процедуры my_get_text = p_decorate(get_text) из примера выше.
def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper
@p_decorate
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
print get_text("John")
# Outputs <p>lorem ipsum, John dolor sit amet</p>
Now let’s consider we wanted to decorate our get_text function by 3 other functions.
Теперь представим, что нам нужно задекорировать не одной а тремя функциями последовательно, добавив дополнительные теги strong и div.
Определим эти три функции-декоратора.
def p_decorate(func):
def func_wrapper(name):
return "<p>{0}</p>".format(func(name))
return func_wrapper
def strong_decorate(func):
def func_wrapper(name):
return "<strong>{0}</strong>".format(func(name))
return func_wrapper
def div_decorate(func):
def func_wrapper(name):
return "<div>{0}</div>".format(func(name))
return func_wrapper
With the basic approach, decorating get_text would be along the lines of
При стандартном подходе, чтобы задекорировать всеми тремя нужно выполнить такой код:
get_text = div_decorate(p_decorate(strong_decorate(get_text)))
Однако при использовании синтаксиса декоратора Python это можно написать гораздо проще и понятней.
@div_decorate
@p_decorate
@strong_decorate
def get_text(name):
return "lorem ipsum, {0} dolor sit amet".format(name)
print get_text("John")
# Outputs <div><p><strong>lorem ipsum, John dolor sit amet</strong></p></div>
Что если нам нужно менять тег, в который мы помещаем вывод декорируемой функции из примеров выше.
Очевидно, для этого тег необходимо передавать в качестве параметра декоратору:
def decorator(opentag,closetag):
def real_decorator(decfunc):
def wrapper(name):
return opentag+' '+ decfunc(name) +' '+closetag
return wrapper
return real_decorator
В таком случае, при декорировании, передаем эти параметры:
@decorator('<h1>','</h1>')
def myf(name):
return name
print myf('Dima')