How decorators work in Python?

19

@Elizeu Santos asked in the Portuguese Python group of facebook the following:

  

"Talk to people, I'm studying python and something I'm not understanding   are the decorators. I faced them doing the flask tuition, they can   make me clear up? "

(Facebook is not cool to answer questions that include Portuguese and mixed code in comments - here is much better formatted for that - so I brought the question here, to paste the answer I wrote to it)

    
asked by anonymous 06.07.2014 / 05:39

1 answer

22

A function in Python is an object like any other - when we do def func(): ... , the name func is associated with the function body that is defined after the def command.

A decorator is a function (or another callable object) that takes a function as a parameter and returns a function. This new function that is returned by the decorator is what is associated with the original function name.

That is, suppose a decorator deco , if used with the syntax of @ does exactly the same as this here:

def func():
   # faz coisas
func = deco(func)

This code snippet is the same as:

@deco
def func():
   #faz coisas

In both cases, after this code snippet, the name func is associated with the object that was returned by the call the deco function. In general this object is a function that calls the function func original, but does something before or after calling it.

For example, a decorator who simply records how many times the functions he decorates were called, in a global variable contador can be declared like this:

contador = 0

def contar_acessos(funcao_decorada):
    # não sabemos quantos parâmetros existirão na chamada
    # da função funcao_decorada, então recebemos *args e **kw
    def nova_func(*args, **kw):
         global contador
         contador += 1
         # e chamamos a função original, com os parâmetros recebidos:
         return funcao_decorada(*args, **kw)
    # retornamos a função "nova_func" - que só faz 
    # incrementar o contador e  retornar o valor da chamada à função original
    return nova_func

# agora vamos usar o decorador:
@contar_acessos
def soma(a, b):
    return a + b

And when we paste this code into the interactive interpreter and call soma sometimes, we have:

>>> contador
0
>>> soma(2,2)
4
>>> soma(3,2)
5
>>> soma("a", "b")
'ab'
>>> contador
3

Then, in this simple case, all that is done is to increase the value of the counter variable before calling the same function that was decorated (and that within the function nova_funcao is named funcao_decorada since it is passed as a parameter to the outside function, contar_acessos ).

But you can sophisticate the thing - you can create a function that saves the return value of the function decorated in a dictionary, for example, there you make a simple cache (memoize). You can simply register the decorated function in a list of "functions that do something", and return it without creating a new decorator function. I believe this is what Flask does - it notes its function decorated as a function that responds to an address in the URL.

    
06.07.2014 / 05:39