Módulo 12 - Decoradores

Preservando a identidade com functools.wraps

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Observar que a função decorada passa a mostrar wrapper no lugar do nome original.
  • Entender por que isso atrapalha depuração, ajuda e documentação.
  • Aplicar @functools.wraps(func) sobre o wrapper para preservar a identidade.
  • Adotar functools.wraps como parte fixa de todo decorador que você escrever.

O problema e a cura, em resumo

Nosso decorador já aceita qualquer função e devolve o resultado. Mas ele criou um problema discreto. Como o nome da função passou a apontar para o wrapper, quando você pergunta o nome ou a documentação da função decorada, o Python responde com os dados do wrapper, não da função original. Na prática, uma função somar decorada passa a se chamar wrapper e perde a docstring que você escreveu. Isso parece detalhe, mas incomoda muito na hora de depurar um erro, gerar documentação automática ou usar o help() no terminal.

def com_moldura(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@com_moldura
def somar(a, b):
    "Soma dois números e devolve o total."
    return a + b

print(somar.__name__)   # wrapper   (deveria ser somar)
print(somar.__doc__)    # None      (a docstring sumiu)

Sem cuidado, a função decorada esquece o próprio nome e a própria documentação.

A cura de uma linha

A biblioteca padrão já resolveu isso para você, no módulo functools. Ele traz um decorador chamado wraps, feito exatamente para ser aplicado sobre o wrapper. O que o functools.wraps faz é copiar para o wrapper os metadados da função original: o nome, a docstring, o módulo e outros atributos internos. Você o usa como @functools.wraps(func) na linha imediatamente acima da definição do wrapper, passando a função original que está sendo decorada. Repare que aqui aparece um decorador decorando o próprio wrapper de outro decorador, o que é uma boa prova de que você entendeu a mecânica.

import functools

def com_moldura(func):
    @functools.wraps(func)          # copia nome e docstring de func
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@com_moldura
def somar(a, b):
    "Soma dois números e devolve o total."
    return a + b

print(somar.__name__)   # somar
print(somar.__doc__)    # Soma dois números e devolve o total.

Com @functools.wraps(func), a função decorada mantém nome e docstring reais.

Fechando a boa prática

A partir daqui, o modelo mental do decorador completo tem três camadas: a função externa recebe func, o wrapper aceita *args e **kwargs e devolve o resultado com return, e o wrapper leva @functools.wraps(func) por cima para preservar a identidade. Esse é o esqueleto que você vai repetir em qualquer decorador sério. O wraps não muda o que o decorador faz; ele apenas evita que a função decorada minta sobre quem é. É barato, cabe em uma linha e evita dores de cabeça futuras com depuração e documentação. Na próxima aula, colocamos tudo isso em prática com casos de uso reais.

Teste rápido

Para que serve aplicar @functools.wraps(func) sobre o wrapper?

Perguntas frequentes

O que quebra se eu não usar functools.wraps?
Nada trava de imediato, mas a função decorada passa a se identificar como wrapper e perde a docstring. Isso atrapalha mensagens de erro, o help() no terminal, ferramentas de documentação automática e qualquer código que inspecione o nome ou a assinatura da função. É um problema silencioso, por isso o wraps é considerado boa prática.
Onde exatamente coloco o @functools.wraps(func)?
Na linha imediatamente acima da def wrapper, dentro do decorador, passando a função original como argumento: @functools.wraps(func). Ele decora o wrapper, copiando para ele os metadados de func. Não confunda com a linha @seu_decorador, que fica sobre a função que você quer decorar no uso.
Preciso importar alguma coisa?
Sim, o functools faz parte da biblioteca padrão, então basta import functools no topo do arquivo, sem instalar nada. Você também pode escrever from functools import wraps e usar apenas @wraps(func). Os dois estilos funcionam igual.
O functools.wraps é um decorador também?
É. Ele é um decorador que você aplica sobre o wrapper. Por isso a linha aparece com o símbolo @ e recebe a função original como argumento. Ver um decorador sendo usado dentro de outro decorador é um bom sinal de que a mecânica ficou clara para você.
Ele copia a assinatura de argumentos também?
Ele copia metadados como nome, docstring, módulo e o dicionário de atributos, e ajusta o __wrapped__ apontando para a função original. Ferramentas modernas de inspeção conseguem, a partir disso, recuperar a assinatura real. Para o dia a dia, o ganho principal é ter nome e docstring corretos de volta.
Vale usar wraps mesmo em decorador simples?
Vale. O custo é uma linha e o benefício é a função decorada nunca mentir sobre a própria identidade. Adotar functools.wraps como parte fixa do formato do decorador evita que você esqueça justamente quando o metadado correto fizer falta, no meio de uma depuração.

Fontes

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