Módulo 12 - Decoradores

Prática: um decorador de log e tempo

12 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 cronometro que mede o tempo de execução de uma função.
  • Escrever um decorador registrar que loga o nome, os argumentos e o retorno de cada chamada.
  • Aplicar os dois na mesma função e entender a ordem de empilhamento.
  • Conferir que nome e docstring seguem corretos graças ao functools.wraps.

O que você vai construir

Esta é uma prática guiada; escreva o código no Playground do curso e rode a cada passo. O objetivo é fixar, com as suas mãos, o formato completo do decorador que você viu no módulo inteiro. Não decore os exemplos anteriores; tente escrever de memória e só confira depois. A meta é ao final ter dois decoradores prontos, o cronometro e o registrar, que funcionam em qualquer função e podem ser combinados. Vá com calma e rode cada versão antes de passar para a próxima.

Antes de começar, relembre o esqueleto que se repete em todo decorador. Você importa functools no topo. A função externa recebe a função a decorar no parâmetro func. Dentro dela, um wrapper com assinatura (*args, **kwargs) faz o trabalho extra e chama func(*args, **kwargs), guardando e devolvendo o resultado. Sobre o wrapper vai o @functools.wraps(func). E a externa termina com return wrapper. Guarde esse molde: as duas tarefas abaixo são só preencher o miolo.

Passo a passo dos dois decoradores

Comece pelo cronometro. Dentro do wrapper, marque o relógio com time.perf_counter antes de chamar a função, guarde o resultado da chamada, marque o relógio de novo e imprima a diferença de forma legível, usando o nome real da função via func.__name__. Devolva o resultado no fim. Depois faça o registrar seguindo a mesma estrutura: antes da chamada, imprima o nome e os argumentos recebidos; depois, imprima o valor devolvido; então retorne esse valor. Repare que nada disso muda o que a função original faz; os decoradores apenas observam.

import functools
import time

def cronometro(func):
    "Mede e imprime o tempo de execução da função."
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        inicio = time.perf_counter()
        resultado = func(*args, **kwargs)
        duracao = time.perf_counter() - inicio
        print(f"[tempo] {func.__name__}: {duracao:.6f}s")
        return resultado
    return wrapper

def registrar(func):
    "Loga o nome, os argumentos e o retorno de cada chamada."
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[log] {func.__name__} args={args} kwargs={kwargs}")
        resultado = func(*args, **kwargs)
        print(f"[log] {func.__name__} -> {resultado!r}")
        return resultado
    return wrapper

@cronometro
def fatorial(n):
    "Calcula o fatorial de n."
    total = 1
    for k in range(2, n + 1):
        total *= k
    return total

print(fatorial(5))
# [tempo] fatorial: 0.000004s
# 120
print(fatorial.__name__, "-", fatorial.__doc__)
# fatorial - Calcula o fatorial de n.

cronometro e registrar prontos; graças ao wraps, fatorial mantém nome e docstring.

Agora combine os dois na mesma função e observe a ordem. Ao empilhar @registrar sobre @cronometro, o de baixo (cronometro) envolve a função primeiro, e o de cima (registrar) envolve o resultado já cronometrado. Por isso as mensagens de log aparecem por fora das de tempo. Troque a ordem das duas linhas e rode de novo para ver a diferença; é a melhor forma de sentir como o empilhamento funciona. Esse controle fino é justamente o que torna decoradores tão úteis em código de verdade.

@registrar
@cronometro
def somar_lista(numeros):
    "Soma os números de uma lista."
    return sum(numeros)

somar_lista([10, 20, 30])
# [log] somar_lista args=([10, 20, 30],) kwargs={}
# [tempo] somar_lista: 0.000003s
# [log] somar_lista -> 60

# Equivale a: somar_lista = registrar(cronometro(somar_lista))

Empilhados, os decoradores se aplicam de baixo para cima; o log fica por fora do tempo.

Conferindo o que você construiu

Se você chegou até aqui com os dois decoradores rodando, fechou o módulo com o formato completo dominado. Recapitule o que praticou: função externa recebendo func, wrapper genérico com *args e **kwargs, o trabalho extra antes e depois da chamada, o return do resultado e o functools.wraps preservando a identidade. Você também viu, na prática, que empilhar decoradores é aplicá-los de baixo para cima. Esse é o vocabulário que aparece em qualquer código Python profissional, de frameworks web a suítes de teste. No próximo módulo, o assunto são os geradores, que resolvem outro tipo de problema com a mesma elegância.

Teste rápido

Em @registrar acima de @cronometro sobre uma função, qual decorador envolve a função primeiro?

Perguntas frequentes

Meu cronometro sempre mostra um tempo pequeno demais, está errado?
Provavelmente não. Funções simples rodam em microssegundos, então um valor como 0.000004s é esperado. O tempo varia por máquina e por carga do sistema. Para ver números maiores, cronometre uma função que faça mais trabalho, como somar um intervalo grande com sum(range(10_000_000)).
Por que usar {resultado!r} no log em vez de {resultado}?
O !r pede a representação com repr, que mostra o valor de forma mais explícita, por exemplo com aspas em textos. Em um log, isso ajuda a distinguir o número 60 do texto '60' e a ler estruturas com clareza. É um detalhe pequeno que torna o registro mais útil na hora de investigar.
Se eu trocar a ordem de @registrar e @cronometro, muda o resultado?
O valor devolvido pela função não muda, mas a ordem em que as mensagens aparecem, sim. Com @cronometro por cima, o tempo passa a englobar também o trabalho de log. Rodar as duas ordens e comparar a saída é o melhor jeito de entender o empilhamento na prática.
Preciso da docstring nas funções para a prática funcionar?
A prática funciona sem docstring, mas incluí-las serve para você comprovar o efeito do functools.wraps: ao imprimir func.__doc__ da função decorada, a docstring correta aparece. Sem o wraps, ela sumiria. É uma boa forma de ver o benefício com os próprios olhos.
Posso reaproveitar esses decoradores em outros exercícios?
Sim, e é a intenção. cronometro e registrar são genéricos: funcionam com qualquer função graças ao wrapper com *args e **kwargs. Guarde-os num arquivo de utilidades e aplique com um @ onde quiser medir tempo ou acompanhar chamadas. Esse reaproveitamento é justamente o valor do decorador.
O que vem depois de decoradores no curso?
O próximo módulo trata de geradores, um recurso que produz valores sob demanda com a palavra yield, poupando memória em sequências grandes. Assim como os decoradores, os geradores partem de funções e trazem elegância a um problema comum. A base de funções que você reforçou aqui continua sendo o alicerce.

Fontes

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