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

Velocidade

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.

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 / 5

O 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.