Декораторы Python используют понятие замыкание: объяснение здесь. Почитайте о замыканиях, чтобы попасть в декораторы.

Decorator принимает функцию, добавляет некоторую функциональность и возвращает ее.

Поэтому декораторы эффективно делают это:

target = decorator_function(target)

Таким образом, если функция-декоратор не возвращает функцию, декоративная функция не может быть вызвана.

def decorator_evil(target):
    return False
@decorator_evil
def target(a,b):
    return a + b
target
>False
target(1,2)
>TypeError: 'bool' object is not callable

Теперь давайте познакомимся с концепцией замыкания при использовании в python:

def capitalize(func):
    def uppercase():
        return_string = func()
        return return_string.upper()
    return uppercase
def helloworld():
    return "helloworld"
capitalized_hello = capitalize(helloworld)
print(helloworld())
print(capitalized_hello())
#output
helloworld
HELLOWORLD

То же самое достигается с помощью декораторов Python:

def capitalize(func):
    def uppercase():
        return_string = func()
        return return_string.upper()
    return uppercase
def helloworld():
    return "helloworld"
print(helloworld())
@capitalize
def helloworld():
    return "helloworld"
print(helloworld())
#output
helloworld
HELLOWORLD

Одноразовое и постоянное украшение

Посмотрите эту реализацию, чтобы понять, что декораторы можно вызывать каждый раз с вызовом функции:

def decorator1(target):
    # Do something with the target function
    target.attribute = 1
    print 'outer'
    return target
def decorator2(target):
    # Do something with the target function
    
    def wrapper(*args):
        print 'inner'
        return target(*args)
    print 'outer'
    wrapper.attribute = 1
    return wrapper
# Here is the decorator, with the syntax '@function_name'
@decorator1
def target(a,b):
    return a + b
print target(1,2)
@decorator2
def target(a,b):
    return a + b
print target(1,3)
#Output
outer
3
outer
inner
4

Прикованные декораторы

Это когда декораторы используются последовательно.

def decorator1(target):
    # Do something with the target function
    target.attribute = 1
    print 'outer'
    return target
def decorator2(target):
    # Do something with the target function
    
    def wrapper(*args):
        print 'inner'
        return target(*args)
    print 'outer'
    wrapper.attribute = 1
    return wrapper
# Here is the decorator, with the syntax '@function_name'
@decorator1
def target(a,b):
    return a + b
print target(1,2)
@decorator2
def target(a,b):
    return a + b
print target(1,3)
#Output
outer
3
outer
inner
4

Обратите внимание, что здесь звездное украшение выполняется первым в первом определении и вторым во втором определении.

Примеры из реального мира

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

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

import time
def timing_function(some_function):
"""
    Outputs the time a function takes
    to execute.
    """
def wrapper():
        t1 = time.time()
        some_function()
        t2 = time.time()
        return "Time it took to run the function: " + str((t2 - t1)) + "\n"
    return wrapper
@timing_function
def my_function():
    num_list = []
    for num in (range(0, 10000)):
        num_list.append(num)
    print("\nSum of all the numbers: " + str((sum(num_list))))
print(my_function())

*args и **kwargs

*args позволяют использовать любое количество аргументов в функции. Это необходимо для объяснения многократного украшения

def test_var_args(f_arg, *args):
    print "first normal arg:", f_arg
    for arg in args:
        print "another arg through *argv :", arg
test_var_args('yasoob','python','eggs','test')
# *args is taken as a tuple ('yasoob','python','eggs','test')
#output
first normal arg: yasoob
another arg through *argv : python
another arg through *argv : eggs
another arg through *argv : test

**kwargs (аргументы с ключевыми словами) аналогичны *args, за исключением того, что это аргументы, для которых требуются ключевые слова.

def greet_me(index,**kwargs):
    print 'Roll_no: ',index
    if kwargs is not None:
        for key, value in kwargs.iteritems():
            print "%s == %s" %(key,value)
 
greet_me(1, name="Rohan", age="27")
# **kwargs is taken as a dict {'name':"Rohan", 'age':"27"}
#output
Roll_no:  1
age == 27
name == Rohan

передача формальных аргументов, *args и **kwargs:

some_func(fargs,*args,**kwargs)

Теперь давайте вернемся к примеру, в котором вычислялось время, необходимое для реализации функции. Эта функция должна быть универсальной с любым количеством аргументов с помощью some_function(*args,**kwargs).

The time to implement the function is calculated by the decorator here.
import time
def timing_function(some_function):
"""
    Outputs the time a function takes
    to execute.
    """
def wrapper():
        t1 = time.time()
        some_function(*args,**kwargs) # note the difference
        t2 = time.time()
        return "Time it took to run the function: " + str((t2 - t1)) + "\n"
    return wrapper
@timing_function
def my_function(arg1, arg2, arg3, arg4, arg5): pass
@timing function
def my_function2(arg1, arg2): pass
print(my_function())

Обратите внимание, что args — это кортеж, который распаковывается в переменные с помощью *args, а kwargs — это словарь, который распаковывается с помощью **kwargs. Подробнее читайте здесь: https://www.agiliq.com/blog/2012/06/understanding-args-and-kwargs/

Декораторы с аргументами

def tags(tag_name):
    def tags_decorator(func):
        def func_wrapper(name):
            return "<{0}>{1}</{0}>".format(tag_name, func(name))
        return func_wrapper
    return tags_decorator
@tags("p")
def get_text(name):
    return "Hello "+name
print get_text("John")
# Outputs <p>Hello John</p>

Условное оформление

Когда условие передается в декоратор

def skip_if(conditional, message):
    def dec(func):
        def inner(*args, **kwargs):
            if not conditional:
                # Execute the function as normal
                return func(*args, **kwargs)
            else:
                # Skip the function, print the message
                print(message)
        return inner
    return dec
@skip_if(True, 'Will not execute, because I hate doge')
def doge():
    print('very print')
doge()
#output
Will not execute, because I hate doge

Альтернативное использование — это когда условие передается в саму функцию (кажется более реалистичным):

def check_age(func):
    def wrapped_func(age, *args, **kwargs):
        if age<18:
            return 'You are under 18, not allowed!'
        else: 
            return func(age, *args, **kwargs)
    return wrapped_func
@check_age
def give_drinks(age,money):
    drinks = money/10
    return str(drinks) + ' drinks given'
print give_drinks(25,200)
print give_drinks(14,1000)
#output
20 drinks given
You are under 18, not allowed!

Дополнительные ресурсы для справки:

  1. http://www.siafoo.net/article/68 [Расширенные концепции декораторов]
  2. http://code.activestate.com/recipes/578222-a-python-decorator-that-re-executes-the-function-o/
  3. https://www.artima.com/weblogs/viewpost.jsp?thread=240845

Источники:

  1. https://medium.com/@rohanrony/code-better-with-python-closure-39e4b5f859d
  2. https://www.programiz.com/python-programming/decorator
  3. https://www.codementor.io/moyosore/a-dive-into-python-closures-and-decorators-part-1-9mpr98pgr
  4. https://www.codementor.io/moyosore/a-dive-into-python-closures-and-decorators-part-2-ab2enoyjg
  5. https://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/
  6. https://realpython.com/primer-on-python-decorators/
  7. https://www.thecodeship.com/patterns/guide-to-python-function-decorators/
  8. https://www.codeschool.com/blog/2016/05/12/a-guide-to-python-decorators/