Módulo 5 - Decoradores avançados
Recap do decorador e o functools.wraps
9 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Relembrar que um decorador é uma função que recebe e devolve outra função.
- Enxergar o problema: o wrapper ingênuo apaga __name__ e __doc__.
- Usar @functools.wraps para preservar a identidade da função original.
- Reconhecer functools.wraps como padrão obrigatório de todo decorador.
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: Recap do decorador e o functools.wraps.
Os objetivos desta aula. Relembrar que um decorador é uma função que recebe e devolve outra função. Enxergar o problema: o wrapper ingênuo apaga __name__ e __doc__. Usar @functools.wraps para preservar a identidade da função original. Reconhecer functools.wraps como padrão obrigatório de todo decorador.
Veja o essencial, parte por parte.
Como o decorador funciona por dentro. Um decorador é uma função que recebe uma função e devolve outra no lugar dela.
O problema silencioso da identidade perdida. Mensagens de log e de erro passam a citar wrapper em vez do nome real da função.
A solução em uma linha: functools.wraps. A biblioteca padrão resolve o problema com functools.wraps.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Como o decorador funciona por dentro
No Intermediário você viu que um decorador é uma função que recebe outra função como argumento e devolve uma nova função no lugar dela. A sintaxe com arroba é só um atalho: escrever @registrar em cima de def somar é exatamente o mesmo que escrever somar = registrar(somar) logo depois da definição. Nada de mágico acontece; o nome somar passa a apontar para a função que registrar devolveu. Entender isso com clareza é o que permite avançar para os decoradores mais elaborados deste módulo.
def registrar(funcao):
def wrapper(*args, **kwargs):
print(f"chamando {funcao.__name__}")
return funcao(*args, **kwargs)
return wrapper
@registrar
def somar(a, b):
return a + b
# @registrar equivale a: somar = registrar(somar)
print(somar(2, 3)) # chamando somar / 5O decorador recebe somar, devolve wrapper, e somar passa a ser wrapper.
O wrapper recebe *args e **kwargs para poder envolver qualquer função, com qualquer assinatura. Ele faz o trabalho extra, no caso imprimir uma linha de log, e então chama a função original passando adiante todos os argumentos que recebeu. O return da função original é devolvido sem alteração, para que quem chamou continue recebendo o resultado esperado. Esse esqueleto, recebe função, define wrapper, devolve wrapper, é o mesmo em todo decorador.
O problema silencioso da identidade perdida
Há um efeito colateral que passa despercebido até dar dor de cabeça. Depois de decorar somar, o nome que o Python enxerga para a função não é mais somar, e sim wrapper. Isso acontece porque somar agora aponta para a função interna do decorador, que se chama wrapper. A documentação (a docstring) também some, porque a docstring visível é a do wrapper, que não tem nenhuma. Para código de brincadeira isso não importa, mas ferramentas de depuração, logs, geração de documentação e o próprio pytest usam esses metadados.
def registrar(funcao):
def wrapper(*args, **kwargs):
return funcao(*args, **kwargs)
return wrapper
@registrar
def somar(a, b):
"Soma dois numeros e devolve o total."
return a + b
print(somar.__name__) # wrapper (deveria ser somar)
print(somar.__doc__) # None (a docstring sumiu)Sem wraps, a função decorada perde o próprio nome e a documentação.
A solução em uma linha: functools.wraps
A biblioteca padrão resolve o problema com functools.wraps. Você o aplica como decorador sobre a função wrapper, passando a função original como argumento. Ele copia os metadados importantes da original para o wrapper: o __name__, a __doc__, o __module__ e outros. O resultado é que a função decorada continua se apresentando com a própria identidade, mesmo estando embrulhada. A partir daqui, todo decorador deste curso usa wraps, e você deveria fazer o mesmo em qualquer código de verdade.
import functools
def registrar(funcao):
@functools.wraps(funcao)
def wrapper(*args, **kwargs):
return funcao(*args, **kwargs)
return wrapper
@registrar
def somar(a, b):
"Soma dois numeros e devolve o total."
return a + b
print(somar.__name__) # somar
print(somar.__doc__) # Soma dois numeros e devolve o total.Uma linha, @functools.wraps(funcao), preserva nome e docstring.
Sem functools.wraps
- somar.__name__ vira wrapper
- somar.__doc__ vira None
- help() e logs mostram o wrapper
- Ferramentas de doc geram texto errado
Com functools.wraps
- somar.__name__ continua somar
- somar.__doc__ mantém a descrição real
- help() e logs mostram a função original
- Ferramentas de doc funcionam corretamente
Teste rápido
Por que aplicar @functools.wraps(funcao) na função wrapper de um decorador?
Perguntas frequentes
- O functools.wraps é sempre necessário?
- Em qualquer decorador que você pretenda usar de verdade, sim. Ele é barato, cabe em uma linha e evita bugs sutis em logs, documentação e ferramentas que inspecionam a função. A convenção profissional é: todo wrapper leva @functools.wraps(funcao). Só dá para dispensar em rascunhos descartáveis.
- Qual a diferença entre functools.wraps e functools.update_wrapper?
- São a mesma coisa por baixo. O update_wrapper é a função que copia os metadados de uma função para outra; o wraps é um atalho pensado para usar como decorador dentro de outro decorador. Na prática você quase sempre usa wraps, por ser mais limpo. Ambos vêm do módulo functools da biblioteca padrão.
- O que exatamente o wraps copia?
- Por padrão copia __module__, __name__, __qualname__, __doc__ e __dict__, e atualiza o __wrapped__ para apontar para a função original. Isso cobre o que ferramentas de depuração, documentação e teste costumam consultar. Você raramente precisa mexer nesse conjunto padrão.
- Por que o wrapper usa *args e **kwargs?
- Para envolver qualquer função, independentemente de quantos parâmetros ela tenha. O *args captura os argumentos posicionais e o **kwargs os nomeados, e o wrapper os repassa intactos para a função original. Assim o mesmo decorador serve para funções com assinaturas diferentes.
- Posso ver a função original depois de decorá-la?
- Sim, quando você usa wraps. Ele guarda a referência em funcao_decorada.__wrapped__, que aponta para a função original sem o embrulho. Isso é útil em testes, quando você quer chamar a versão sem o comportamento extra do decorador.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.