Декоратори – це така конструкція мови, яка може змінити поведінку функції, методу, класу тощо. не змінюючи її саму.
Це ідеальний спосіб щось змінити в програмі без модифікації коду об’єкта, що змінюється.
Розберемо процес декорування функції.
Для початку розглянемо деякі особливості функцій.
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')