Decorators em Python: o guia prático com functools.wraps

Entenda decorators em Python de verdade: por que funções são objetos, como o @ funciona por dentro, o wrapper com args e kwargs, functools.wraps e um decorator útil de medir tempo.

Revisado pela equipe editorial ValorFinalDocumentação oficial do Python (functools) e Python.org
Calcule agora: JSON FormatterResultado na hora, de graça e sem cadastro.

Decorator é uma função que embrulha outra função para acrescentar comportamento sem tocar no código original. É um dos recursos que mais aparece em Python de verdade (frameworks web, testes, classes) e que mais assusta quem vê o símbolo @ pela primeira vez. Este guia mostra o mecanismo por dentro, do porquê funções serem objetos até um decorator útil de medir tempo, tudo com código que roda. Para praticar com playground no navegador, exercícios e certificado, use o curso de Python avançado, gratuito.

Resposta rápida

  • Um decorator recebe uma função e devolve outra com algo a mais.
  • @nome é atalho para funcao = nome(funcao).
  • Use *args, **kwargs no wrapper para aceitar qualquer função.
  • Sempre aplique @functools.wraps para preservar nome e documentação.

Antes de tudo: funções são objetos

O decorator só faz sentido depois que você aceita uma ideia: em Python, função é um valor como qualquer outro. Dá para guardar uma função numa variável, passar como argumento e retornar de dentro de outra função.

def saudacao(nome):
    return "Ola, " + nome

f = saudacao          # guardando a funcao numa variavel
print(f("Ana"))        # Ola, Ana

def executar(funcao, valor):
    return funcao(valor)   # recebendo uma funcao como argumento

print(executar(saudacao, "Bruno"))  # Ola, Bruno

Se função pode ser passada e retornada, então dá para escrever uma função que fabrica outra função. É exatamente isso que um decorator faz.

O primeiro decorator, passo a passo

Vamos criar um decorator que avisa antes e depois de a função rodar. Ele recebe a função original, define um wrapper por dentro que acrescenta as mensagens, e devolve esse wrapper:

def registrar(funcao):
    def wrapper(*args, **kwargs):
        print("Comecando", funcao.__name__)
        resultado = funcao(*args, **kwargs)
        print("Terminou", funcao.__name__)
        return resultado
    return wrapper

@registrar
def somar(a, b):
    return a + b

print(somar(2, 3))

O @registrar em cima de somar é um atalho: é o mesmo que escrever somar = registrar(somar). A partir daí, chamar somar(2, 3) passa pelo wrapper, que imprime as mensagens e chama a função real no meio. O *args, **kwargs deixa o wrapper aceitar qualquer função, com qualquer quantidade de argumentos.

Preservando a identidade com functools.wraps

Há um efeito colateral: depois de embrulhada, somar.__name__ vira "wrapper", porque a função visível agora é o wrapper. Isso confunde debug, help() e ferramentas de teste. A biblioteca padrão resolve com functools.wraps:

import functools

def registrar(funcao):
    @functools.wraps(funcao)
    def wrapper(*args, **kwargs):
        resultado = funcao(*args, **kwargs)
        return resultado
    return wrapper

Com essa linha, o wrapper copia o nome e a documentação da função original. É uma boa prática fixa: todo decorator que você escrever deve usar functools.wraps.

Um decorator útil: medir o tempo

Um caso real e comum é medir quanto uma função demora, sem espalhar código de cronômetro por todo lado:

import functools
import time

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

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

somar_ate(1_000_000)

Coloque @cronometrar em cima de qualquer função e ela passa a relatar o próprio tempo. Nenhuma linha da lógica original mudou. Esse é o ponto forte do decorator: separar o comportamento extra da regra de negócio.

Decorators que você já usa sem perceber

Depois de entender o mecanismo, esses símbolos deixam de ser mágica: você lê o que cada um embrulha e por quê.

Erros comuns com decorators

O curso de Python avançado aprofunda decorators, geradores, context managers e mais, com playground no navegador, exercícios e certificado, sem custo. Se ainda faltam as bases de classes e coleções, o curso de Python intermediário vem antes na trilha.

Fontes

Conclusão

Decorator é uma função que recebe outra e devolve uma versão com algo a mais. O @ é só um atalho, o *args, **kwargs deixa o wrapper servir a qualquer função e o functools.wraps preserva o nome original. Depois de escrever um cronômetro ou um log como decorator, os @ dos frameworks deixam de ser mistério. Para praticar com playground, exercícios e certificado gratuitos, use o curso de Python avançado e conheça todos os cursos gratuitos do ValorFinal.

Calculadoras deste guia

Fontes oficiais

Links externos para os documentos oficiais consultados na construção desta página. O conteúdo deles pode mudar sem aviso; em caso de divergência, vale sempre a fonte oficial.

Como validamos os cálculos

Os valores citados neste guia são estimativos e baseados em fontes oficiais (Documentação oficial do Python (functools) e Python.org). Eles podem variar conforme convenção coletiva, situação individual e atualizações da legislação. Entenda nossa metodologia em como validamos os cálculos.

Perguntas frequentes

O que é um decorator em Python?
É uma função que recebe outra função e devolve uma versão dela com algo a mais, sem mexer no código original. Você escreve @nome em cima da função e, a partir daí, toda chamada passa antes pelo decorator. É o jeito de acrescentar comportamento repetido (medir tempo, registrar log, checar permissão) a várias funções sem copiar esse código em cada uma.
Para que serve o @ em cima de uma função?
O @nome é um atalho. Escrever @meu_decorator antes de def tarefa() é o mesmo que escrever tarefa = meu_decorator(tarefa) depois da definição. Os dois fazem a mesma coisa: substituem a função pela versão embrulhada pelo decorator. O @ só deixa isso mais legível e coloca a intenção logo acima da função.
Por que usar functools.wraps num decorator?
Quando você embrulha uma função, a de dentro perde o nome e a documentação originais: passam a ser os do wrapper. functools.wraps copia esses dados de volta para a função embrulhada. Sem ele, ferramentas de debug, o help() e a introspecção mostram wrapper no lugar do nome real. É uma linha que evita dor de cabeça, então use sempre.
Qual a diferença entre decorator e função normal?
Toda função normal recebe dados e devolve um resultado. O decorator é uma função que trabalha com outra função: recebe a função como argumento e devolve uma nova função. Isso é possível porque, em Python, funções são objetos que podem ser passados e retornados como qualquer valor. O decorator só aproveita esse fato.
Preciso saber decorators para trabalhar com Python?
Para escrever scripts simples, não. Mas você vai encontrar decorators cedo ao usar frameworks: @app.route no Flask, @property em classes, @staticmethod, @pytest.fixture nos testes. Entender como funcionam por dentro faz esses símbolos deixarem de ser mágica e viram ferramenta que você lê e escreve com confiança.
Onde pratico decorators sem instalar Python?
No navegador. O curso de Python avançado do ValorFinal traz um playground que roda o código na própria página, então você escreve decorators, testa com functools.wraps e vê a saída na hora, sem configurar ambiente. Escrever e quebrar o exemplo de propósito é a forma mais rápida de entender o mecanismo.