Decoradores são um conceito avançado, mas extremamente útil. Eles são a chave para entender como muitos frameworks Python funcionam "por baixo dos panos" e para escrever código mais modular e limpo. Um decorador em Python é uma função que recebe outra função como argumento, adiciona alguma funcionalidade a ela e retorna uma nova função (ou a função modificada), sem alterar o código da função original diretamente.
Decoradores são amplamente usados para estender o comportamento de funções ou classes de forma limpa e reutilizável. Eles são basicamente uma "embalagem" que você coloca em volta de uma função para modificar ou aprimorar seu comportamento.
Para entender decoradores, imagine uma função que "envolve" outra.
def minha_funcao_original():
print("Executando a função original.")
def funcao_envolvedora(func):
def wrapper():
print("Antes de chamar a função original.")
func() # Chama a função original
print("Depois de chamar a função original.")
return wrapper
# Usando a função envolvedora manualmente
funcao_modificada = funcao_envolvedora(minha_funcao_original)
funcao_modificada()
# Saída:
# Antes de chamar a função original.
# Executando a função original.
# Depois de chamar a função original.
Neste exemplo, funcao_envolvedora
é o que chamamos de função decoradora. Ela recebe minha_funcao_original
, define uma nova função interna wrapper
que adiciona comportamento antes e depois, e então retorna essa wrapper
.
@
(Açúcar Sintático)Python oferece uma sintaxe especial, o @
(arroba), que torna o uso de decoradores muito mais limpo e legível. O @
é apenas "açúcar sintático" para o processo de envolvimento manual que vimos acima.
A sintaxe:
@nome_do_decorador
def minha_funcao():
pass
é equivalente a:
def minha_funcao():
pass
minha_funcao = nome_do_decorador(minha_funcao)
Exemplo Prático de Decorador:
Vamos criar um decorador que mede o tempo de execução de uma função.
import time
def medir_tempo(func):
"""
Um decorador que mede o tempo de execução de uma função.
"""
def wrapper(*args, **kwargs): # *args e **kwargs permitem que a função decorada aceite qualquer argumento
inicio = time.time()
resultado = func(*args, **kwargs) # Executa a função original
fim = time.time()
print(f"A função '{func.__name__}' levou {fim - inicio:.4f} segundos para executar.")
return resultado
return wrapper
# Usando o decorador com a sintaxe @
@medir_tempo
def minha_funcao_lenta():
"""Uma função que simula um trabalho demorado."""
print("Iniciando trabalho lento...")
time.sleep(2) # Simula um atraso de 2 segundos
print("Trabalho lento concluído.")
return "Dados Processados"
@medir_tempo
def somar_numeros(a, b):
print(f"Somando {a} e {b}...")
return a + b
# Chamando as funções decoradas
valor_retornado = minha_funcao_lenta()
print(f"Função lenta retornou: {valor_retornado}")
soma = somar_numeros(10, 20)
print(f"Soma: {soma}")
Saída esperada:
Iniciando trabalho lento...
Trabalho lento concluído.
A função 'minha_funcao_lenta' levou 2.00xx segundos para executar.
Função lenta retornou: Dados Processados
Somando 10 e 20...
A função 'somar_numeros' levou 0.00xx segundos para executar.
Soma: 30