Decoradores Python: Guia Pratico — 2025 | Python Brasil
Aprenda a criar e usar decoradores em Python com exemplos praticos. Domine closures, functools.wraps e decoradores com argumentos no seu codigo.
Decoradores sao uma das funcionalidades mais elegantes do Python. Eles permitem modificar o comportamento de funcoes e classes sem alterar o codigo original. Neste guia pratico, a gente vai entender como decoradores funcionam por baixo dos panos e criar varios exemplos uteis para o dia a dia.
O Que Sao Decoradores?
Um decorador e uma funcao que recebe outra funcao como argumento, adiciona alguma funcionalidade e retorna uma nova funcao. Em termos simples, e um wrapper que envolve uma funcao existente.
A sintaxe com @ e apenas um atalho sintatico. Estas duas formas sao equivalentes:
# Com sintaxe @
@meu_decorador
def minha_funcao():
pass
# Equivalente sem @
def minha_funcao():
pass
minha_funcao = meu_decorador(minha_funcao)
Para entender decoradores, primeiro precisamos entender que em Python, funcoes sao objetos de primeira classe. Isso significa que podemos passar funcoes como argumentos, retorna-las de outras funcoes e atribui-las a variaveis.
Criando Seu Primeiro Decorador
Vamos comecar com um decorador simples que registra quando uma funcao e chamada:
import functools
def log_chamada(func):
"""Decorador que registra chamadas de funcao."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Chamando {func.__name__} com args={args}, kwargs={kwargs}")
resultado = func(*args, **kwargs)
print(f"{func.__name__} retornou {resultado}")
return resultado
return wrapper
@log_chamada
def somar(a, b):
"""Soma dois numeros."""
return a + b
@log_chamada
def saudacao(nome, idioma="pt"):
"""Retorna uma saudacao."""
if idioma == "pt":
return f"Ola, {nome}!"
return f"Hello, {nome}!"
# Uso
somar(3, 5)
# Chamando somar com args=(3, 5), kwargs={}
# somar retornou 8
saudacao("Maria", idioma="en")
# Chamando saudacao com args=('Maria',), kwargs={'idioma': 'en'}
# saudacao retornou Hello, Maria!
Repare no uso de functools.wraps(func). Sem ele, a funcao decorada perde seu nome e docstring originais. Sempre use functools.wraps nos seus decoradores.
Decorador de Temporizacao
Um caso de uso muito comum e medir o tempo de execucao de funcoes:
import functools
import time
def cronometrar(func):
"""Mede o tempo de execucao de uma funcao."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
inicio = time.perf_counter()
resultado = func(*args, **kwargs)
fim = time.perf_counter()
duracao = fim - inicio
print(f"{func.__name__} executou em {duracao:.4f} segundos")
return resultado
return wrapper
@cronometrar
def processar_dados(n):
"""Simula processamento pesado."""
total = sum(i ** 2 for i in range(n))
return total
processar_dados(1_000_000)
# processar_dados executou em 0.1523 segundos
Decoradores com Argumentos
Quando voce precisa que o decorador aceite parametros, e necessario criar mais um nivel de funcao aninhada:
import functools
def repetir(vezes):
"""Decorador que repete a execucao de uma funcao."""
def decorador(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
resultado = None
for i in range(vezes):
print(f"Execucao {i + 1} de {vezes}")
resultado = func(*args, **kwargs)
return resultado
return wrapper
return decorador
@repetir(vezes=3)
def enviar_notificacao(mensagem):
"""Envia uma notificacao."""
print(f"Notificacao: {mensagem}")
return True
enviar_notificacao("Backup concluido")
# Execucao 1 de 3
# Notificacao: Backup concluido
# Execucao 2 de 3
# Notificacao: Backup concluido
# Execucao 3 de 3
# Notificacao: Backup concluido
Decorador de Cache (Memoizacao)
O Python ja oferece functools.lru_cache, mas e instrutivo criar o seu proprio:
import functools
def cache_simples(func):
"""Cache simples para funcoes puras."""
_cache = {}
@functools.wraps(func)
def wrapper(*args):
if args in _cache:
print(f"Cache hit para {args}")
return _cache[args]
resultado = func(*args)
_cache[args] = resultado
return resultado
wrapper.cache_info = lambda: f"Tamanho do cache: {len(_cache)}"
wrapper.cache_limpar = lambda: _cache.clear()
return wrapper
@cache_simples
def fibonacci(n):
"""Calcula o n-esimo numero de Fibonacci."""
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(30)) # 832040
print(fibonacci.cache_info()) # Tamanho do cache: 31
Na pratica, use functools.lru_cache que ja vem otimizado:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci_otimizado(n):
if n < 2:
return n
return fibonacci_otimizado(n - 1) + fibonacci_otimizado(n - 2)
Decorador de Validacao
Decoradores sao otimos para validar entradas antes de executar a logica principal:
import functools
def validar_tipos(**tipos_esperados):
"""Valida os tipos dos argumentos de uma funcao."""
def decorador(func):
@functools.wraps(func)
def wrapper(**kwargs):
for param, tipo in tipos_esperados.items():
if param in kwargs and not isinstance(kwargs[param], tipo):
raise TypeError(
f"Parametro '{param}' deve ser {tipo.__name__}, "
f"recebeu {type(kwargs[param]).__name__}"
)
return func(**kwargs)
return wrapper
return decorador
@validar_tipos(nome=str, idade=int)
def cadastrar_usuario(nome, idade):
"""Cadastra um usuario no sistema."""
return {"nome": nome, "idade": idade}
# Funciona normalmente
cadastrar_usuario(nome="Ana", idade=28)
# Levanta TypeError
# cadastrar_usuario(nome="Ana", idade="vinte")
# TypeError: Parametro 'idade' deve ser int, recebeu str
Decorador de Retry
Muito util para operacoes que podem falhar temporariamente, como chamadas de rede:
import functools
import time
def retry(tentativas=3, delay=1, excecoes=(Exception,)):
"""Repete a funcao em caso de falha."""
def decorador(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
ultima_excecao = None
for tentativa in range(1, tentativas + 1):
try:
return func(*args, **kwargs)
except excecoes as e:
ultima_excecao = e
print(f"Tentativa {tentativa}/{tentativas} falhou: {e}")
if tentativa < tentativas:
time.sleep(delay)
raise ultima_excecao
return wrapper
return decorador
@retry(tentativas=3, delay=2, excecoes=(ConnectionError, TimeoutError))
def buscar_dados_api(url):
"""Busca dados de uma API externa."""
import requests
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
Empilhando Decoradores
Voce pode aplicar varios decoradores a uma mesma funcao. Eles sao aplicados de baixo para cima:
@cronometrar
@log_chamada
def calcular(x, y):
return x * y
# Equivale a: cronometrar(log_chamada(calcular))
Decoradores em Classes
Decoradores tambem podem ser implementados como classes usando o metodo __call__:
class ContadorChamadas:
"""Conta quantas vezes uma funcao foi chamada."""
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.chamadas = 0
def __call__(self, *args, **kwargs):
self.chamadas += 1
print(f"{self.func.__name__} foi chamada {self.chamadas} vezes")
return self.func(*args, **kwargs)
@ContadorChamadas
def processar(dado):
return dado.upper()
processar("teste") # processar foi chamada 1 vezes
processar("outro") # processar foi chamada 2 vezes
Boas Praticas
Ao trabalhar com decoradores, siga estas recomendacoes:
- Sempre use
functools.wrapspara preservar metadados da funcao original - Mantenha decoradores com responsabilidade unica, cada um faz apenas uma coisa
- Documente claramente o que o decorador faz e quais parametros aceita
- Teste decoradores separadamente, como qualquer outra funcao
- Evite efeitos colaterais inesperados dentro de decoradores
- Prefira decoradores do stdlib como
lru_cache,propertyestaticmethodquando possiveis
Conclusao
Decoradores sao uma ferramenta poderosa que torna o codigo Python mais limpo e reutilizavel. Com o entendimento de closures e funcoes de ordem superior, voce pode criar decoradores para logging, cache, validacao, controle de acesso e muito mais. Comece com os exemplos deste guia e adapte-os para as necessidades do seu projeto. A pratica constante vai tornar o uso de decoradores algo natural no seu dia a dia como desenvolvedor Python.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português