Módulo 12 - Decoradores

Casos de uso reais de decoradores

11 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Reconhecer quando um decorador é a ferramenta certa: comportamento repetido em várias funções.
  • Escrever um decorador que mede o tempo de execução de uma função.
  • Escrever um decorador que registra cada chamada em um log.
  • Escrever um decorador que valida argumentos antes de executar a função.

Quando o decorador é a escolha certa

Decorador não serve para tudo, e sair espalhando @ por todo lado deixa o código pior. Ele é a ferramenta certa para um caso específico: quando o mesmo comportamento auxiliar precisa envolver muitas funções diferentes, sem ser o assunto principal de nenhuma delas. Pense em medir quanto tempo cada função demora, registrar quem foi chamado e com quais dados, ou conferir se os argumentos são válidos antes de rodar. Escrever isso dentro de cada função geraria repetição e sujeira. Um decorador isola esse comportamento transversal em um lugar só e o aplica com uma linha.

Os três exemplos a seguir usam o mesmo formato completo que você montou ao longo do módulo: função externa recebendo func, wrapper com *args e **kwargs, return do resultado e functools.wraps por cima. Só muda o que acontece dentro do wrapper. Isso reforça a boa notícia do assunto: uma vez entendido o esqueleto, cada novo decorador é só preencher o miolo.

Medir tempo e registrar chamadas

O primeiro decorador cronometra. Ele marca o relógio de alta precisão antes de chamar a função, executa a função guardando o resultado, marca o relógio de novo e mostra a diferença. Usamos time.perf_counter, feito para medir intervalos curtos. O segundo decorador registra: antes de executar, imprime o nome da função e os argumentos recebidos; depois, mostra o que ela devolveu. Em um sistema real, esse print viraria uma gravação no módulo de logging, mas a ideia é a mesma. Note como os dois seguem o formato idêntico, mudando só o miolo.

import functools
import time

def cronometro(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        inicio = time.perf_counter()
        resultado = func(*args, **kwargs)
        fim = time.perf_counter()
        print(f"{func.__name__} levou {fim - inicio:.6f}s")
        return resultado
    return wrapper

def registrar(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"chamando {func.__name__} args={args} kwargs={kwargs}")
        resultado = func(*args, **kwargs)
        print(f"{func.__name__} devolveu {resultado!r}")
        return resultado
    return wrapper

@cronometro
def somar_ate(n):
    return sum(range(n))

@registrar
def saudar(nome, saudacao="Olá"):
    return f"{saudacao}, {nome}!"

print(somar_ate(1_000_000))
# somar_ate levou 0.0123s   (o número varia por máquina)
# 499999500000
saudar("Ana", saudacao="Oi")
# chamando saudar args=('Ana',) kwargs={'saudacao': 'Oi'}
# saudar devolveu 'Oi, Ana!'

cronometro mede o tempo; registrar loga entrada e saída. Mesmo formato, miolos diferentes.

Validar argumentos antes de rodar

O terceiro caso é a validação. Aqui o wrapper faz uma checagem antes de deixar a função rodar e, se algo estiver errado, levanta um erro em vez de chamar a função. É útil para blindar funções que só fazem sentido com certos valores, como recusar números negativos onde só cabem positivos. O ponto importante é que a função original fica limpa, cuidando só do cálculo, enquanto a regra de entrada mora no decorador. Se a mesma regra vale para várias funções, você a escreve uma vez e a aplica com um @ em cada uma.

import functools

def exigir_positivos(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        for valor in args:
            if isinstance(valor, (int, float)) and valor < 0:
                raise ValueError(f"{func.__name__} não aceita valores negativos: {valor}")
        return func(*args, **kwargs)
    return wrapper

@exigir_positivos
def media(a, b):
    return (a + b) / 2

print(media(8, 10))   # 9.0
print(media(8, -4))
# ValueError: media não aceita valores negativos: -4

O decorador barra argumentos negativos antes de a função rodar; media fica limpa.

Teste rápido

Qual é a vantagem de validar argumentos com um decorador em vez de dentro de cada função?

Perguntas frequentes

Quando NÃO devo usar um decorador?
Quando o comportamento é específico de uma única função e não se repete. Nesse caso, escrever a lógica direto na função é mais claro. Decorador compensa quando o mesmo extra (log, tempo, validação, cache) precisa envolver muitas funções. Usar @ por moda, sem essa repetição, só adiciona indireção sem ganho.
Por que usar time.perf_counter e não time.time?
O perf_counter é um relógio de alta precisão feito para medir intervalos, imune a ajustes do relógio do sistema. O time.time devolve a hora do dia, com menos precisão para trechos curtos. Para cronometrar execução, perf_counter é a escolha recomendada pela documentação.
Em produção, uso print no decorador de log?
Nos exemplos usamos print para ficar simples de ler. Em um programa real, troque por uma chamada ao módulo logging da biblioteca padrão, que permite níveis, formatação e destino configurável. A estrutura do decorador continua idêntica; muda só o que você faz com a informação da chamada.
O decorador de validação impede a função de rodar?
Sim, quando encontra um argumento inválido. Ele checa antes e, se algo estiver errado, levanta um erro (como ValueError) sem nunca chamar a função original. Se tudo estiver certo, ele deixa a função rodar normalmente e devolve o resultado. Assim a função só executa com entradas válidas.
Posso combinar mais de um desses decoradores?
Pode. Empilhe as linhas @ sobre a mesma função, por exemplo @cronometro e @registrar juntos. Eles se aplicam de baixo para cima e cada um envolve o resultado do outro. Só cuide da ordem, pois ela muda em que sequência os comportamentos acontecem ao redor da função.
Esses decoradores mudam o resultado da função?
Não, e é de propósito. cronometro e registrar apenas observam: medem, imprimem e devolvem o mesmo resultado da função original. O de validação pode interromper com um erro, mas quando deixa rodar, devolve o resultado intacto. Decoradores de observação e guarda não devem falsear o valor devolvido.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.