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.
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: Prática: um decorador de log e tempo.
Os objetivos desta aula. 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.
Veja o essencial, parte por parte.
O que você vai construir. Você vai escrever cronometro, que mede o tempo de execução de uma função.
Passo a passo dos dois decoradores. Comece pelo cronometro.
Conferindo o que você construiu. Se você chegou até aqui com os dois decoradores rodando, fechou o módulo com o formato completo dominado.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
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.