
Passo 1 de 7
Acrescentar comportamento com uma função envoltória
Crie um decorador simples que adiciona uma mensagem antes de chamar uma função, mantendo a responsabilidade do cálculo separada.
Trilha de aprendizado · Nível 9 · Tutorial 6
Ao concluir, você será capaz de acrescentar comportamento a funções por meio de decoradores, preservando o encaminhamento das chamadas e os principais metadados da função original.
Acrescentar comportamento com uma função envoltória
Crie um decorador simples que adiciona uma mensagem antes de chamar uma função, mantendo a responsabilidade do cálculo separada. 3 min
Aplicar @ e separar definição de chamada
Entenda quando o decorador é aplicado e quando o wrapper é executado nas chamadas posteriores. 3 min
Encaminhar argumentos e devolver o resultado
Generalize o wrapper para encaminhar qualquer combinação de argumentos e preservar o valor devolvido pela função original. 3 min
Preservar as exceções da função original
Acrescente ações a um wrapper sem esconder falhas que fazem parte do contrato da função original. 3 min
Preservar metadados com functools.wraps
Use functools.wraps para que uma função decorada continue apresentando os metadados da função que ela envolve. 5 min
Entender dois decoradores empilhados
Veja como duas camadas de decoradores são aplicadas e executadas, mantendo os metadados em cada wrapper. 4 min
Aplicar e revisar um decorador completo
Pratique em um único script um decorador que encaminha chamadas, preserva resultados, exceções e metadados, combinado com uma segunda camada. 5 min

Passo 1 de 7
Crie um decorador simples que adiciona uma mensagem antes de chamar uma função, mantendo a responsabilidade do cálculo separada.
Um decorador recebe uma função e devolve o objeto que ficará associado ao nome usado pelo programa. Neste primeiro modelo, ele devolve outra função: o wrapper.
Como em uma closure, o wrapper interno continua acessando a função recebida, mesmo depois que o decorador termina. Assim, ele pode executar uma ação extra e então chamar a função original.
O nome público passa a apontar para o wrapper; o wrapper mantém uma referência privada para a função de cálculo original.

A função original continua existindo; o nome público foi reassociado ao wrapper.
Comece com uma função de cálculo que recebe dois valores. O decorador recebe essa função em funcao e cria wrapper, também com dois parâmetros. Antes de encaminhar a chamada, o wrapper exibe uma mensagem.
Observe que a última linha do decorador devolve a função wrapper, sem parênteses.
def avisar_antes(funcao):
def wrapper(preco, quantidade):
print("Calculando o total...")
return funcao(preco, quantidade)
return wrapper
def calcular_total(preco, quantidade):
return preco * quantidade
Dica
return wrapper entrega a função para uso posterior. Já return wrapper(...) a executaria naquele momento e devolveria seu resultado. Um decorador precisa devolver o wrapper, não o resultado de uma chamada dele.
A aplicação explícita chama o decorador com a função original e guarda o wrapper no mesmo nome público. Depois disso, chamar calcular_total(12, 3) entra primeiro no wrapper; ele mostra a mensagem e chama a função de cálculo.
Acrescente este trecho abaixo do código anterior e execute o script.
calcular_total = avisar_antes(calcular_total)
resultado = calcular_total(12, 3)
print(resultado)
# Saída:
# Calculando o total...
# 36Exemplo
calcular_total sabe multiplicar preço por quantidade. avisar_antes sabe acrescentar um aviso antes de uma chamada. O wrapper combina esses comportamentos sem colocar o print dentro da função de cálculo.
Organize os eventos da aplicação explícita até a obtenção do resultado.

Passo 2 de 7
Entenda quando o decorador é aplicado e quando o wrapper é executado nas chamadas posteriores.
A sintaxe @anunciar acima de uma função é uma forma compacta de aplicar o decorador assim que a instrução def é executada.
Estas duas formas têm o mesmo efeito operacional:
<code>@anunciar
def calcular_total():
...</code>
<code>def calcular_total():
...
calcular_total = anunciar(calcular_total)</code>
Depois dessa aplicação, o nome calcular_total passa a se referir ao wrapper retornado por anunciar.
Execute este script para observar os momentos distintos.
def anunciar(funcao):
print("DECORAR")
def wrapper():
print("ANTES")
return funcao()
return wrapper
@anunciar
def cumprimentar():
print("FUNÇÃO ORIGINAL")
print("--- primeira chamada ---")
cumprimentar()
print("--- segunda chamada ---")
cumprimentar()Ao chegar à definição decorada, Python cria a função cumprimentar e executa anunciar(cumprimentar). Por isso, DECORAR aparece uma única vez.
Nesse momento, o corpo de cumprimentar ainda não é executado. Ele só será chamado pelo wrapper quando você executar cumprimentar().
A aplicação troca a referência do nome; cada chamada posterior percorre o wrapper e então alcança a função original.

A decoração ocorre uma vez na definição; o wrapper é executado uma vez por chamada.
Qual é a sequência de linhas impressas pelo script apresentado?

Passo 3 de 7
Generalize o wrapper para encaminhar qualquer combinação de argumentos e preservar o valor devolvido pela função original.
Um wrapper útil não conhece antecipadamente a assinatura de cada função que poderá envolver. Por isso, ele recebe *args e **kwargs, repassa esses valores para a função capturada e devolve seu resultado.
O contrato aqui é simples: para cada chamada ao wrapper, a função original é chamada uma vez com os mesmos argumentos. O valor produzido volta ao chamador.
Observe que o wrapper fica no meio do percurso: ele recebe a chamada, pode acrescentar uma ação e não deve interromper o fluxo de argumentos nem do resultado.

Os argumentos seguem para dentro; o resultado retorna para fora.
A ação antes da chamada é adicionada pelo wrapper. A função calcular_total continua responsável apenas pelo cálculo.
def registrar_chamada(funcao):
def wrapper(*args, **kwargs):
print("Iniciando cálculo")
resultado = funcao(*args, **kwargs)
print("Cálculo concluído")
return resultado
return wrapper
@registrar_chamada
def calcular_total(preco, quantidade=1, desconto=0):
return preco * quantidade - desconto
valor = calcular_total(50, quantidade=2, desconto=10)
print(valor) # 90Exemplo
Na chamada calcular_total(50, quantidade=2, desconto=10):
args = (50,) e kwargs = {"quantidade": 2, "desconto": 10}.90.90.*args e **kwargs não tornam argumentos inválidos válidos: se a função original rejeitaria uma chamada, ela continuará rejeitando-a.
Se o wrapper apenas executar funcao(*args, **kwargs), a função original pode calcular corretamente, mas quem chamou a função decorada receberá None. Toda função que termina sem return devolve None.
Guarde o resultado em uma variável quando precisar fazer algo depois da chamada; então, devolva essa variável.
A função original retorna 14, mas o wrapper não devolve esse valor.
def anunciar(funcao):
def wrapper(*args, **kwargs):
print("Chamando função")
funcao(*args, **kwargs)
return wrapper
@anunciar
def dobrar(numero):
return numero * 2
resultado = dobrar(7)
print(resultado) # NoneDica
Use return funcao(*args, **kwargs) quando nada precisar acontecer após a chamada. Se houver uma ação posterior, use resultado = ..., execute a ação e termine com return resultado.
Complete o trecho para que medir receba os argumentos originais e devolva o resultado da função:
def medir(funcao):
def wrapper(*args, **kwargs):
print("Antes")
resultado = funcao(____)
print("Depois")
____ resultado
return wrapper
Passo 4 de 7
Acrescente ações a um wrapper sem esconder falhas que fazem parte do contrato da função original.
Se o wrapper chama a função original e não usa try/except, uma exceção lançada pela função original atravessa o wrapper naturalmente até quem fez a chamada. Assim, o comportamento acrescentado antes da chamada ocorre, mas a falha continua visível para o chamador.
O wrapper pode iniciar uma ação e depois encaminhar a chamada. O resultado volta apenas no caminho de sucesso; uma exceção segue para fora no caminho de erro.

Sem captura no wrapper, a exceção não é convertida nem interrompida por ele.
A mensagem anterior é exibida tanto para chamadas que dão certo quanto para chamadas que falham.
def registrar(funcao):
def wrapper(*args, **kwargs):
print("Iniciando chamada")
return funcao(*args, **kwargs)
return wrapper
@registrar
def dividir(total, quantidade):
return total / quantidade
print(dividir(10, 2)) # 5.0
print(dividir(10, 0)) # ZeroDivisionError continua chegando ao chamadorUma instrução logo depois de funcao(*args, **kwargs) só é alcançada se a chamada terminar normalmente. Já um bloco finally executa ao encerrar a tentativa, tanto após um resultado quanto durante a propagação de uma exceção.
Guarde o resultado, faça a ação de encerramento e devolva o mesmo valor. Se houver erro, o finally registra o encerramento e a exceção continua sendo propagada.
def registrar_encerramento(funcao):
def wrapper(*args, **kwargs):
print("Antes da chamada")
try:
resultado = funcao(*args, **kwargs)
print("Chamada concluída com sucesso")
return resultado
finally:
print("Chamada encerrada")
return wrapperAtenção
Capturar ZeroDivisionError e devolver 0 faz a função decorada deixar de sinalizar divisão por zero: isso altera seu contrato. Do mesmo modo, um return ou uma nova exceção no finally pode substituir o resultado ou ocultar a falha original.
Se você precisa agir especificamente no caminho de erro, registre a informação e use raise sem argumento para relançar a mesma exceção.
def registrar_falha(funcao):
def wrapper(*args, **kwargs):
try:
return funcao(*args, **kwargs)
except Exception:
print("A chamada falhou")
raise
return wrapperQual wrapper registra o encerramento, devolve o resultado em caso de sucesso e preserva uma exceção da função original?

Passo 5 de 7
Use functools.wraps para que uma função decorada continue apresentando os metadados da função que ela envolve.
Quando você decora uma função, o nome público passa a apontar para o wrapper. Sem um cuidado adicional, consultas como __name__ e __doc__ descrevem o wrapper, não a função original.
Isso é uma perda aparente: a função original continua existindo, capturada pelo closure. O que mudou foi o objeto associado ao nome decorado.
Sem wraps, o nome somar referencia o wrapper; o wrapper mantém uma referência interna à função original.

O nome público passa a acessar o wrapper; a função original continua acessível dentro dele.
Exemplo
def registrar(funcao):
def wrapper(*args, **kwargs):
print("chamada registrada")
return funcao(*args, **kwargs)
return wrapper
@registrar
def somar(a: int, b: int) -> int:
"""Devolve a soma de dois inteiros."""
return a + b
print(somar.__name__) # wrapper
print(somar.__doc__) # NoneA docstring e as anotações continuam na função original, mas somar agora é o objeto wrapper.
Importe wraps explicitamente e use @wraps(funcao) imediatamente acima da definição do wrapper. Assim, wraps atualiza os principais metadados do wrapper a partir da função recebida.
O encaminhamento de argumentos e o retorno continuam sendo responsabilidade do wrapper.
from functools import wraps
def registrar(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print("chamada registrada")
return funcao(*args, **kwargs)
return wrapper
@registrar
def somar(a: int, b: int) -> int:
"""Devolve a soma de dois inteiros."""
return a + b
print(somar.__name__) # somar
print(somar.__doc__) # Devolve a soma de dois inteiros.
print(somar.__annotations__) # {'a': int, 'b': int, 'return': int}
print(somar(2, 3)) # chamada registrada\n5Dica
wraps preserva metadados, mas não conserta a lógica do wrapper. Ainda é necessário encaminhar *args e **kwargs, devolver o resultado e não ocultar exceções quando elas devem se propagar.
Além de atualizar metadados como nome, documentação e anotações, @wraps(funcao) cria __wrapped__. Esse atributo referencia a função imediatamente envolvida pela camada atual.
Por isso, somar.__wrapped__(2, 3) chama a função original diretamente e não executa a mensagem registrada pelo wrapper. É útil para acessar a camada envolvida, mas não substitui a interface decorada.
A chamada pública percorre o wrapper. A chamada por __wrapped__ pula somente o comportamento acrescentado por essa camada.

__wrapped__ desvia da camada atual; a interface decorada continua sendo o nome público.
Relacione o atributo ao efeito esperado após aplicar @wraps(funcao).
Toque em um item e depois no par correspondente.
Compare mentalmente um decorador que usa @wraps(funcao) com outro que não usa. Por que __name__ e __doc__ podem mudar? Cite também uma responsabilidade que continua sendo do código do wrapper.
Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 7
Veja como duas camadas de decoradores são aplicadas e executadas, mantendo os metadados em cada wrapper.
Quando há dois decoradores, o que está mais perto da definição recebe primeiro a função original. Assim:
@a
@b
def funcao():
...é equivalente a:
funcao = a(b(funcao))Primeiro, b recebe a função original e devolve seu wrapper. Depois, a recebe esse wrapper e devolve a camada externa associada ao nome funcao.
O nome público aponta para o wrapper criado por a; ele envolve o wrapper de b, que por sua vez envolve a função original.

Aplicação: função original → b → a. O resultado final é a camada externa de a.
Execute este exemplo em um arquivo Python para observar a sequência.
from functools import wraps
def a(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print("a: antes")
resultado = funcao(*args, **kwargs)
print("a: depois")
return resultado
return wrapper
def b(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print("b: antes")
resultado = funcao(*args, **kwargs)
print("b: depois")
return resultado
return wrapper
@a
@b
def calcular(valor):
"""Devolve o dobro do valor."""
print("função original")
return valor * 2
print(calcular(4))Na chamada calcular(4), entra-se pela camada externa e avança-se até a função original. Depois que ela retorna com sucesso, as ações posteriores acontecem no caminho de volta:
a: antesb: antesfunção originalb: depoisa: depois8Os return dos dois wrappers encaminham o resultado até o chamador.
Para o código anterior, coloque os cinco eventos internos na ordem em que são impressos por calcular(4).
Se você inverter os decoradores para @b sobre @a, a composição passa a ser b(a(funcao)). A entrada será por b e a saída terminará em b.
Como ambos usam @wraps(funcao), cada wrapper preserva os metadados da função que recebeu. Além disso, __wrapped__ aponta somente para a camada imediatamente envolvida.
Com @a sobre @b, uma remoção de camada não chega diretamente à função original.

calcular.__wrapped__ acessa o wrapper de b; um segundo .__wrapped__ alcança a função original.
Se o código fosse alterado para @b acima de @a, explique a ordem dos marcadores em uma chamada bem-sucedida. Depois, diga para qual objeto calcular.__wrapped__ apontaria nesse arranjo.
Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 7
Pratique em um único script um decorador que encaminha chamadas, preserva resultados, exceções e metadados, combinado com uma segunda camada.
Nesta prática, a função decorada continuará aceitando os mesmos argumentos, devolverá o mesmo resultado e deixará exceções chegarem ao chamador. A camada extra apenas registra eventos.
Use @wraps(funcao) no wrapper: assim, o nome público conserva os metadados da função de cálculo e aponta para ela por meio de __wrapped__.
A chamada atravessa as camadas, chega à função original e retorna pelo caminho inverso. Se a função original falhar, a exceção também atravessa as camadas que não a capturam.

Argumentos entram pela camada externa; resultado ou exceção retorna ao chamador.
No seu editor, crie um arquivo chamado pratica_decorador.py. Copie o código abaixo e complete somente o corpo de registrar no local indicado. Depois, execute python pratica_decorador.py no terminal da pasta do arquivo.
O decorador marcar já está pronto e permite observar a composição das duas camadas.
from functools import wraps
def registrar(funcao):
# COMPLETE AQUI
# Crie um wrapper com @wraps(funcao).
# Ele deve imprimir antes, chamar funcao com *args e **kwargs,
# imprimir depois e devolver o resultado.
pass
def marcar(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print("marca: entrada")
try:
return funcao(*args, **kwargs)
finally:
print("marca: saída")
return wrapper
@marcar
@registrar
def dividir(total: float, quantidade: float = 1) -> float:
"""Divide total pela quantidade."""
return total / quantidade
print("resultado:", dividir(10, quantidade=2))
try:
dividir(10, quantidade=0)
except ZeroDivisionError as erro:
print("erro chegou ao chamador:", type(erro).__name__)
print("nome:", dividir.__name__)
print("doc:", dividir.__doc__)
print("camada interna:", dividir.__wrapped__.__name__)
print("original:", dividir.__wrapped__.__wrapped__.__name__)Compare a sua implementação com esta versão. O print posterior só ocorre após uma chamada bem-sucedida; se funcao lançar uma exceção, não há valor para retornar e a exceção continua seu percurso.
def registrar(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print("registro: antes")
resultado = funcao(*args, **kwargs)
print("registro: depois")
return resultado
return wrapperExemplo
Na chamada válida, a sequência será:
marca: entrada
registro: antes
registro: depois
marca: saída
resultado: 5.0Na chamada com quantidade=0, você verá marca: entrada, registro: antes, marca: saída e então erro chegou ao chamador: ZeroDivisionError. Não aparece registro: depois, pois a divisão não terminou.
Os últimos quatro prints confirmam dividir como nome e a docstring original. A primeira referência __wrapped__ alcança o wrapper de registrar; a segunda alcança a função original. A ordem observada confirma @marcar como camada externa e @registrar como camada interna.
Em poucas linhas, registre: como seu wrapper encaminha argumentos e resultado; o que ocorreu na divisão por zero; como __name__, __doc__ e __wrapped__ se comportaram; e qual foi a ordem das duas camadas.
Escreva pelo menos 180 caracteres (0/180).
Resumo
Você integrou os elementos de um decorador não parametrizado.
*args e **kwargs à função envolvida.ZeroDivisionError.@wraps(funcao) para manter nome, documentação e __wrapped__.Parabéns! Você concluiu: Criar decoradores com functools.wraps
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks