Módulo 5 - Decoradores avançados
Empilhar decoradores e a ordem de aplicação
9 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Empilhar dois ou mais decoradores sobre a mesma função.
- Entender que a aplicação acontece de baixo para cima.
- Entender que a execução do wrapper acontece de cima para baixo.
- Prever a saída de uma pilha de decoradores antes de rodar.
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: Empilhar decoradores e a ordem de aplicação.
Os objetivos desta aula. Empilhar dois ou mais decoradores sobre a mesma função. Entender que a aplicação acontece de baixo para cima. Entender que a execução do wrapper acontece de cima para baixo. Prever a saída de uma pilha de decoradores antes de rodar.
Veja o essencial, parte por parte.
A aplicação acontece de baixo para cima. Decoradores empilhados se aplicam de baixo para cima (o mais perto do def primeiro).
A execução como camadas de cebola. Há dois momentos distintos e é fácil confundi-los.
Quando a ordem muda o resultado. O que precisa rodar primeiro a cada chamada? Esse decorador vai por cima.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
A aplicação acontece de baixo para cima
Quando você escreve dois decoradores, um em cima do outro, a função é embrulhada em camadas. A ordem de aplicação, isto é, quem embrulha quem, vai de baixo para cima: o decorador imediatamente acima do def age primeiro, sobre a função original; o decorador de cima age depois, sobre o resultado do primeiro. É a mesma leitura de f = a(b(f)): o b é aplicado antes porque está mais para dentro dos parênteses, e o a envolve o que o b produziu.
import functools
def negrito(funcao):
@functools.wraps(funcao)
def wrapper(*args, **kwargs):
return "<b>" + funcao(*args, **kwargs) + "</b>"
return wrapper
def italico(funcao):
@functools.wraps(funcao)
def wrapper(*args, **kwargs):
return "<i>" + funcao(*args, **kwargs) + "</i>"
return wrapper
@negrito
@italico
def saudacao():
return "ola"
print(saudacao()) # <b><i>ola</i></b>italico envolve a função primeiro (está mais perto); negrito envolve o resultado.
A saída conta a história. O italico, mais próximo do def, embrulha ola e produz uma função que devolve i mais ola mais barra i. O negrito envolve essa função e acrescenta as tags b por fora. Por isso o resultado é b, i, ola, barra i, barra b, de fora para dentro. Se você invertesse a ordem dos dois decoradores, as tags trocariam de lugar. A pilha é literal: quem está por cima fica por fora.
A execução como camadas de cebola
Há dois momentos distintos e é fácil confundi-los. A aplicação, que monta as camadas, acontece uma vez, quando o Python define a função, e vai de baixo para cima. A execução, que percorre as camadas, acontece toda vez que você chama a função, e vai de fora para dentro: o wrapper do decorador de cima roda primeiro, faz sua parte, chama o wrapper de baixo, que faz a dele e chama a função original. Na volta, cada camada completa seu trabalho. É o modelo de cebola.
import functools
def faixa(nome):
def decorador(funcao):
@functools.wraps(funcao)
def wrapper(*args, **kwargs):
print(f"entra {nome}")
resultado = funcao(*args, **kwargs)
print(f"sai {nome}")
return resultado
return wrapper
return decorador
@faixa("externo")
@faixa("interno")
def tarefa():
print("executando tarefa")
tarefa()
# entra externo
# entra interno
# executando tarefa
# sai interno
# sai externoO de cima (externo) entra primeiro e sai por último; o de baixo (interno) fica no meio.
| Momento | Direção | Quando acontece |
|---|---|---|
| Aplicação (montar camadas) | De baixo para cima | Uma vez, ao definir a função |
| Execução (percorrer camadas) | De fora para dentro e de volta | A cada chamada da função |
Aplicação e execução andam em sentidos diferentes; separar as duas evita confusão.
Quando a ordem muda o resultado
Nem sempre a ordem importa, mas quando importa, importa muito. Um caso clássico é combinar um decorador que verifica permissão com um que faz cache. Se o cache fica por fora, uma resposta guardada pode ser servida sem checar a permissão de novo, o que é um problema de segurança. Se a verificação fica por fora, ela roda sempre, antes de qualquer cache. Por isso vale desenhar a pilha de propósito, perguntando o que precisa acontecer primeiro em cada chamada.
A boa notícia é que não há regra a decorar, só um modelo a entender. Aplicação de baixo para cima, execução de fora para dentro. Sempre que uma pilha de decoradores confundir, transforme-a na forma com parênteses e leia devagar. Com um pouco de prática, você passa a prever a saída de uma pilha só de olhar, e a escolher a ordem que produz o comportamento certo, em vez de descobrir por tentativa e erro.
Teste rápido
Em @a seguido de @b sobre uma função f, qual é a equivalência correta?
Perguntas frequentes
- A ordem dos decoradores sempre muda o resultado?
- Nem sempre. Se os decoradores fazem coisas independentes, que não interferem uma na outra, a ordem pode não afetar a saída. Mas quando um decorador transforma o retorno ou depende de rodar antes de outro (como segurança antes de cache), a ordem muda o comportamento. Por segurança, desenhe a pilha de propósito.
- Como lembro qual decorador roda primeiro na execução?
- O que está mais em cima, mais longe do def, roda primeiro na execução e termina por último. Pense em camadas de cebola: o wrapper de fora começa, chama o de dentro, que chama a função original, e a volta acontece na ordem inversa. Aplicação é de baixo para cima; execução é de fora para dentro.
- Existe limite de quantos decoradores posso empilhar?
- Não há limite prático imposto pela linguagem, mas há um limite de bom senso. Cada camada adiciona uma chamada de função e uma indireção a mais. Duas ou três costumam ser suficientes e legíveis; pilhas muito altas ficam difíceis de entender e depurar. Se precisar de muitas, talvez um único decorador mais completo seja melhor.
- Por que o functools.wraps é importante ao empilhar?
- Porque sem ele cada camada apagaria o nome da anterior, e a função final se apresentaria como wrapper, sem relação com a original. Com wraps em cada nível, os metadados da função original atravessam todas as camadas e chegam intactos ao topo. Em pilhas de decoradores, esquecer o wraps confunde ainda mais a depuração.
- Qual a forma mais segura de prever a saída de uma pilha?
- Reescrever a pilha na forma com parênteses. Uma sequência @a, @b, @c sobre f vira f = a(b(c(f))). Aí você lê de dentro para fora para entender a aplicação e de fora para dentro para entender a execução. Essa tradução mecânica elimina quase toda a confusão.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.