Декораторы 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()
#outputWill 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!
Дополнительные ресурсы для справки:
- http://www.siafoo.net/article/68 [Расширенные концепции декораторов]
- http://code.activestate.com/recipes/578222-a-python-decorator-that-re-executes-the-function-o/
- https://www.artima.com/weblogs/viewpost.jsp?thread=240845
Источники:
- https://medium.com/@rohanrony/code-better-with-python-closure-39e4b5f859d
- https://www.programiz.com/python-programming/decorator
- https://www.codementor.io/moyosore/a-dive-into-python-closures-and-decorators-part-1-9mpr98pgr
- https://www.codementor.io/moyosore/a-dive-into-python-closures-and-decorators-part-2-ab2enoyjg
- https://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/
- https://realpython.com/primer-on-python-decorators/
- https://www.thecodeship.com/patterns/guide-to-python-function-decorators/
- https://www.codeschool.com/blog/2016/05/12/a-guide-to-python-decorators/