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 parafuncao = nome(funcao).- Use
*args, **kwargsno wrapper para aceitar qualquer função. - Sempre aplique
@functools.wrapspara 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, BrunoSe 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 wrapperCom 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
@propertytransforma um método em atributo calculado dentro de uma classe.@staticmethode@classmethodajustam como um método recebe o contexto da classe.@app.route(...)no Flask liga uma URL a uma função de resposta.@pytest.fixtureprepara dados reutilizáveis para os testes.
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
- Esquecer de retornar o
wrapperno fim do decorator, o que faz a função virarNone. - Não repassar o retorno da função original, perdendo o resultado da chamada.
- Fixar argumentos no wrapper em vez de usar
*args, **kwargs, o que limita a quais funções o decorator serve. - Deixar de usar
functools.wrapse depois estranhar por que o nome da função sumiu no debug.
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
- Documentação oficial do Python: decorator: definição e referência da linguagem.
- Módulo functools (functools.wraps): referência da biblioteca padrão usada aqui.
- Python.org: site oficial da linguagem.
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.