Декоратори.

Декоратори – це така конструкція мови, яка може змінити поведінку функції, методу, класу тощо. не змінюючи її саму.

Це ідеальний спосіб щось змінити в програмі без модифікації коду об’єкта, що змінюється.

Розберемо процес декорування функції.

Для початку розглянемо деякі особливості функцій.

Присвоєння функції змінної.

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}

“.format(func(name)). При цьому ми викликаємо функцію, що декорується, в тому місці, де хочемо отримати її результат. Наприкінці повертаємо цю вкладену функцію, яка називається обгорткою (wrapper).

Синтаксис декоратора в Python.

Застосування декоратора може бути спрощене використанням символу @.

Цей знак - еквівалент процедури 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')