Como criar decoradores com parâmetros?
Os decoradores (decorators) são muito poderosos e práticos. Eles permitem modificar ou melhorar uma função sem alterar seu código internamente. Isso permite que você mantenha a sua função original intacta e, ao mesmo tempo, adicione novos comportamentos a ela.
Se você vem de outras linguagens pode ser que já tenha visto os decoradores com outro nome, como anotações ou wrappers.
Casos de uso#
Existem muitos casos de uso para decoradores. Alguns exemplos são:
- Autenticação
- Cache
- Log
- Validação
- Tempo de execução
Criando um simples decorador#
Para criar um decorador, você precisa de uma função que receba outra função como parâmetro e retorne uma função. Vamos ver um exemplo:
def decorador(funcao):
def wrapper():
print('Antes da função')
saida = funcao()
print('Depois da função')
return saida
return wrapper
Agora vamos criar uma função que utilize o decorador:
@decorador
def exemplo():
print('Função')
Nota
Observe que para utilizar o decorador, basta colocar o arroba e nome dele acima da função que você quer decorar.
O código completo fica assim:
def decorador(funcao):
def wrapper():
print('Antes da função')
saida = funcao()
print('Depois da função')
return saida
return wrapper
@decorador
def exemplo():
print('Função')
if __name__ == '__main__':
exemplo()
Ao executar este arquivo você verá o seguinte resultado:
$ python3 main.py
Antes da função
Função
Depois da função
Nota
Observe que a função decorador
recebe uma função como parâmetro, essa função é a que vem logo abaixo do @decorador
.
Decorando uma função com parâmetros#
Nos exemplos anteriores, nós decoramos uma função bem simples, mas você pode querer decorar uma função que recebe parâmetros. Para isso, basta adicionar os parâmetros na função wrapper
:
def decorador(funcao):
def wrapper(*args, **kwargs):
print('Antes da função')
saida = funcao(*args, **kwargs)
print('Depois da função')
return saida
return wrapper
@decorador
def exemplo(nome):
print(f'Função {nome}')
if __name__ == '__main__':
exemplo('Lorem')
Ao executar este arquivo você verá o seguinte resultado:
$ python3 main2.py
Antes da função
Função Lorem
Depois da função
Criando um decorador com parâmetros#
Agora chegamos na parte mais interessante deste artigo, criar um decorador com parâmetros. Para isso, vamos criar uma função que recebe os parâmetros do decorador e retorna uma função que recebe a função que será decorada:
def decorador(parametro1, parametro2):
def decorador_antigo(funcao):
def wrapper(*args, **kwargs):
print(f'Antes da função {parametro1} {parametro2}')
saida = funcao(*args, **kwargs)
print(f'Depois da função {parametro1} {parametro2}')
return saida
return wrapper
return decorador_antigo
Observe que a função decorador_antigo
é basicamente a mesma coisa da função decorador
dos exemplos anteriores, a única diferença é que agora ela está dentro de outra função (decorador
).
Você pode estar vendo este exemplo e se perguntando em como isso seria útil, veremos isso mais a frente.
Exemplo 1#
Suponha que você deseja saber quanto tempo uma função demora para ser executada. Para isso, vamos criar um decorador para não ter que modificar a função e além disso o decorador irá receber a unidade de tempo que será exibida.
A ideia é que utilizemos o decorador da seguinte forma:
@timeit(unidade='ms')
def exemplo():
time.sleep(30)
if __name__ == '__main__':
exemplo()
Para isso o decorador ficará assim:
import time
def timeit(unidade):
def decorador_antigo(funcao):
def wrapper(*args, **kwargs):
tempo_inicial = time.time()
saida = funcao(*args, **kwargs)
tempo_final = time.time()
duracao = tempo_final - tempo_inicial
if unidade == 'ms':
print(f'Tempo de execução: {round(duracao * 1000, 2)} ms')
elif unidade == 's':
print(f'Tempo de execução: {round(duracao, 2)} s')
elif unidade == 'min':
print(f'Tempo de execução: {round(duracao / 60, 2)} min')
return saida
return wrapper
return decorador_antigo
O código completo ficará assim:
import time
def timeit(unidade):
def decorador_antigo(funcao):
def wrapper(*args, **kwargs):
tempo_inicial = time.time()
saida = funcao(*args, **kwargs)
tempo_final = time.time()
duracao = tempo_final - tempo_inicial
if unidade == 'ms':
print(f'Tempo de execução: {round(duracao * 1000, 2)} ms')
elif unidade == 's':
print(f'Tempo de execução: {round(duracao, 2)} s')
elif unidade == 'min':
print(f'Tempo de execução: {round(duracao / 60, 2)} min')
return saida
return wrapper
return decorador_antigo
@timeit(unidade='ms')
def exemplo():
time.sleep(30)
if __name__ == '__main__':
exemplo()
Ao executar este arquivo você verá o seguinte resultado:
$ python3 main3.py
Tempo de execução: 0.50 min
Nota
Observe que o decorador timeit
recebe um parâmetro unidade
, que é utilizado para definir a unidade de tempo que será exibida no final da execução da função decorada.
Exemplo 2#
Um outro caso e que é comum é em APIs são decoradores para roteamento. Vamos criar um decorador que recebe o caminho da rota e o método HTTP que será utilizado.
A ideia é que utilizemos o decorador da seguinte forma:
@route('/usuarios', metodos=['GET', 'POST'])
def minha_rota():
pass
Para isso o decorador ficará assim:
def route(caminho, metodos):
def decorador_antigo(funcao):
def wrapper(*args, **kwargs):
print(f'Executando rota {caminho} com o método {metodos}')
saida = funcao(*args, **kwargs)
return saida
return wrapper
return decorador_antigo
O código completo ficará assim:
def route(caminho, metodos):
def decorador_antigo(funcao):
def wrapper(*args, **kwargs):
print(f'Executando rota {caminho} com o método {metodos}')
saida = funcao(*args, **kwargs)
return saida
return wrapper
return decorador_antigo
@route('/usuarios', metodos=['GET', 'POST'])
def minha_rota():
pass
Veja que acabamos de criar um decorador bem parecido com o que o Flask utiliza para criar rotas. Nós não fizemos nada demais, mas serve de inspiração para você criar seus próprios decoradores.