Módulo 5 - Decoradores avançados
Casos reais: log, cronômetro, cache, validação e retry
11 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Escrever um decorador de log e um de cronômetro do zero.
- Usar o functools.lru_cache para memoização pronta da biblioteca padrão.
- Criar um decorador de validação que rejeita argumentos inválidos.
- Montar um decorador de retry configurável para operações que podem falhar.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Casos reais: log, cronômetro, cache, validação e retry.
Os objetivos desta aula. Escrever um decorador de log e um de cronômetro do zero. Usar o functools.lru_cache para memoização pronta da biblioteca padrão. Criar um decorador de validação que rejeita argumentos inválidos. Montar um decorador de retry configurável para operações que podem falhar.
Veja o essencial, parte por parte.
Log e cronômetro, os dois clássicos. Decorador de log: imprime a função chamada e os argumentos, sem tocar no corpo.
Cache pronto com lru_cache. Só funciona com argumentos hasháveis (números, textos, tuplas), não listas ou dicionários.
Validação e retry, decoradores configuráveis. Os dois últimos casos usam a fábrica de decoradores da aula 2, porque precisam de configuração.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Log e cronômetro, os dois clássicos
Os dois decoradores mais comuns de todos são o de log e o de cronômetro, e não é por acaso: ambos acrescentam observação sem mudar o que a função faz. O de log registra que a função foi chamada e com quais argumentos, útil para acompanhar um sistema em funcionamento. O de cronômetro mede o tempo decorrido, ponto de partida de qualquer investigação de performance. Os dois seguem o mesmo esqueleto que você já domina, com wraps no lugar.
import functools
import time
def log(funcao):
@functools.wraps(funcao)
def wrapper(*args, **kwargs):
print(f"-> {funcao.__name__}{args}")
resultado = funcao(*args, **kwargs)
print(f"<- {funcao.__name__} devolveu {resultado!r}")
return resultado
return wrapper
def cronometrar(funcao):
@functools.wraps(funcao)
def wrapper(*args, **kwargs):
inicio = time.perf_counter()
resultado = funcao(*args, **kwargs)
duracao = time.perf_counter() - inicio
print(f"{funcao.__name__} levou {duracao:.4f}s")
return resultado
return wrapper
@cronometrar
@log
def somar(a, b):
return a + b
somar(2, 3)Log e cronômetro empilhados: o cronômetro por fora mede tudo, o log por dentro registra.
Repare em dois detalhes profissionais. O time.perf_counter é o relógio recomendado para medir intervalos curtos, mais preciso que o time.time para essa finalidade. E o !r dentro da f-string chama o repr do valor, mostrando o resultado de um jeito sem ambiguidade nos logs, com aspas em textos, por exemplo. São pequenos cuidados que separam um decorador de exemplo de um decorador de produção.
Cache pronto com lru_cache
Para cache, a regra de ouro é não reinventar a roda. A biblioteca padrão já traz o functools.lru_cache, um decorador que memoiza os resultados: na primeira vez com certos argumentos ele calcula e guarda; nas próximas com os mesmos argumentos, devolve o valor guardado na hora. O exemplo clássico é a sequência de Fibonacci recursiva, que sem cache recalcula os mesmos valores milhões de vezes e com cache fica instantânea. Basta uma linha por cima da função.
import functools
@functools.lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(35)) # 9227465, praticamente instantaneo com cache
print(fib.cache_info()) # estatisticas de acertos e faltas do cacheUma linha, @functools.lru_cache, transforma a recursão lenta em instantânea.
Validação e retry, decoradores configuráveis
Os dois últimos casos usam a fábrica de decoradores da aula 2, porque precisam de configuração. Um decorador de validação verifica os argumentos antes de deixar a função rodar e levanta um erro claro se algo estiver fora do combinado, centralizando uma checagem que se repetiria em muitas funções. Um decorador de retry tenta de novo quando a função falha, útil para operações instáveis por natureza, e recebe quantas tentativas fazer. Ambos reúnem tudo do módulo: wraps, fábrica e tratamento de erro.
import functools
import time
def tentar_de_novo(tentativas=3, espera=0.5):
def decorador(funcao):
@functools.wraps(funcao)
def wrapper(*args, **kwargs):
ultimo_erro = None
for tentativa in range(1, tentativas + 1):
try:
return funcao(*args, **kwargs)
except Exception as erro:
ultimo_erro = erro
print(f"tentativa {tentativa} falhou: {erro}")
time.sleep(espera)
raise ultimo_erro
return wrapper
return decorador
@tentar_de_novo(tentativas=3, espera=1.0)
def operacao_instavel():
raise ConnectionError("servidor fora do ar")
# Tenta 3 vezes, espera 1s entre elas, e por fim relanca o erroRetry como fábrica de decoradores: configura tentativas e espera, relança o erro no fim.
Note o cuidado no retry: ele guarda o último erro e, se todas as tentativas falharem, relança esse erro em vez de engolir a falha em silêncio. Esconder um erro é pior do que deixá-lo aparecer. Esse é o tipo de decisão que o Módulo 12, sobre exceções, aprofunda. Por ora, o que importa é reconhecer que esses cinco decoradores, log, cronômetro, cache, validação e retry, cobrem uma fatia enorme das necessidades reais, e todos saem das técnicas deste módulo.
Teste rápido
Qual é a forma recomendada de adicionar cache (memoização) a uma função pura em Python?
Perguntas frequentes
- Por que usar time.perf_counter em vez de time.time no cronômetro?
- O perf_counter é um relógio de alta resolução feito para medir intervalos, com mais precisão e sem sofrer com ajustes do relógio do sistema. O time.time devolve a hora do relógio de parede, que pode até andar para trás com uma sincronização. Para cronometrar trechos de código, perf_counter é a escolha certa.
- O lru_cache serve para qualquer função?
- Não. Ele exige que os argumentos sejam hasháveis, ou seja, números, textos e tuplas servem, mas listas e dicionários não. E a função precisa ser pura: para os mesmos argumentos, sempre o mesmo resultado, sem efeitos colaterais. Memoizar uma função que lê o horário atual ou grava em disco daria resultados errados.
- O que o decorador de retry deve fazer depois de esgotar as tentativas?
- Relançar o último erro, não escondê-lo. Se a operação falhou em todas as tentativas, quem chamou precisa saber para tratar a situação. Engolir a exceção em silêncio cria bugs difíceis de rastrear. O padrão é guardar o último erro capturado e, ao fim do laço, executar raise para propagá-lo.
- Vale a pena escrever meu próprio decorador de cache?
- Só em casos especiais, como precisar de uma política de expiração por tempo que o lru_cache não oferece. Para memoização comum, o lru_cache da biblioteca padrão é testado, rápido e vem com estatísticas via cache_info(). A regra é preferir a ferramenta pronta e só partir para a sua quando houver um requisito que ela não cobre.
- Posso empilhar esses decoradores reais entre si?
- Pode, e é comum. Cronometrar por fora e logar por dentro, por exemplo, é uma combinação frequente. Só lembre da ordem de aplicação: o de cima envolve o resultado do de baixo. Pense no que precisa medir ou registrar em cada camada e monte a pilha de propósito, como você viu na aula sobre empilhamento.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.