Decoradores Python: como usar e por quê?

Um decorador recebe uma função, adiciona alguma funcionalidade e a retorna. Neste tutorial, você aprenderá como criar um decorador e por que deve usá-lo.

Decoradores em Python

Python tem um recurso interessante chamado decoradores para adicionar funcionalidade a um código existente.

Isso também é chamado de metaprogramação porque uma parte do programa tenta modificar outra parte do programa em tempo de compilação.

Pré-requisitos para decoradores de aprendizagem

Para entender sobre decoradores, devemos primeiro saber algumas coisas básicas em Python.

Devemos estar confortáveis ​​com o fato de que tudo em Python (sim! Até mesmo classes), são objetos. Os nomes que definimos são simplesmente identificadores vinculados a esses objetos. Funções não são exceções, elas também são objetos (com atributos). Vários nomes diferentes podem ser associados ao mesmo objeto de função.

Aqui está um exemplo.

 def first(msg): print(msg) first("Hello") second = first second("Hello")

Resultado

 Olá Olá

Quando você executa o código, ambas funcionam firste secondfornecem a mesma saída. Aqui, os nomes firste secondreferem-se ao mesmo objeto de função.

Agora as coisas começam a ficar mais estranhas.

As funções podem ser passadas como argumentos para outra função.

Se você tem funções usados como map, filtere reduceem Python, então você já sabe sobre isso.

Essas funções que recebem outras funções como argumentos também são chamadas de funções de ordem superior . Aqui está um exemplo de tal função.

 def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result

Chamamos a função da seguinte maneira.

 >>> operate(inc,3) 4 >>> operate(dec,3) 2

Além disso, uma função pode retornar outra função.

 def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()

Resultado

 Olá

Aqui, is_returned()está uma função aninhada que é definida e retornada cada vez que chamamos is_called().

Finalmente, devemos saber sobre fechamentos em Python.

Voltando aos decoradores

Funções e métodos são chamados de chamáveis , pois podem ser chamados.

Na verdade, qualquer objeto que implemente o __call__()método especial é denominado chamável. Portanto, no sentido mais básico, um decorador é um chamável que retorna um chamável.

Basicamente, um decorador recebe uma função, adiciona alguma funcionalidade e a retorna.

 def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")

Quando você executa os seguintes códigos no shell,

 >>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary

No exemplo mostrado acima, make_pretty()é um decorador. Na etapa de atribuição:

 pretty = make_pretty(ordinary)

A função ordinary()foi decorada e a função retornada recebeu o nome pretty.

Podemos ver que a função de decorador adicionou algumas novas funcionalidades à função original. Isso é semelhante a embalar um presente. O decorador atua como um invólucro. A natureza do objeto que foi decorado (presente real dentro) não se altera. Mas agora está bonito (já que foi decorado).

Geralmente, decoramos uma função e a reatribuímos como,

 ordinary = make_pretty(ordinary).

Esta é uma construção comum e, por esse motivo, Python tem uma sintaxe para simplificar isso.

Podemos usar o @símbolo junto com o nome da função do decorador e colocá-lo acima da definição da função a ser decorada. Por exemplo,

 @make_pretty def ordinary(): print("I am ordinary")

é equivalente a

 def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)

This is just a syntactic sugar to implement decorators.

Decorating Functions with Parameters

The above decorator was simple and it only worked with functions that did not have any parameters. What if we had functions that took in parameters like:

 def divide(a, b): return a/b

This function has two parameters, a and b. We know it will give an error if we pass in b as 0.

 >>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero

Now let's make a decorator to check for this case that will cause the error.

 def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)

This new implementation will return None if the error condition arises.

 >>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide

In this manner, we can decorate functions that take parameters.

A keen observer will notice that parameters of the nested inner() function inside the decorator is the same as the parameters of functions it decorates. Taking this into account, now we can make general decorators that work with any number of parameters.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such a decorator will be:

 def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

 def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")

Output

 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************

The above syntax of,

 @star @percent def printer(msg): print(msg)

is equivalent to

 def printer(msg): print(msg) printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

 @percent @star def printer(msg): print(msg)

The output would be:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Artigos interessantes...