Módulo 5 - Decoradores avançados

Decorador que recebe argumentos

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

O que você vai aprender

  • Entender por que um decorador com argumentos precisa de um nível a mais.
  • Construir a fábrica de decoradores de três funções aninhadas.
  • Ler a assinatura de cada nível: parâmetros, função e argumentos da chamada.
  • Aplicar o padrão em um decorador de repetição configurável.

Por que precisa de um nível a mais

Um decorador comum recebe a função e devolve o wrapper: dois níveis. Mas repare na diferença entre @registrar e @repetir(vezes=3). No segundo caso há parênteses e argumentos. Isso significa que @repetir(vezes=3) primeiro executa repetir(vezes=3), e o resultado dessa chamada é que vai decorar a função. Ou seja, repetir(vezes=3) precisa devolver um decorador. É esse nível extra que confunde no começo: a função mais de fora não recebe a função a decorar, ela recebe a configuração.

# Decorador SEM argumentos: dois niveis
@registrar
def somar(a, b):
    return a + b
# equivale a: somar = registrar(somar)

# Decorador COM argumentos: tres niveis
@repetir(vezes=3)
def ola():
    print("ola")
# equivale a: ola = repetir(vezes=3)(ola)
#            \_______/ \__/
#             fabrica    funcao a decorar

Com argumentos, a fábrica é chamada primeiro e devolve o decorador que envolve a função.

Guarde a linha de baixo do exemplo: ola = repetir(vezes=3)(ola). Ela mostra que há duas chamadas em sequência. A primeira, repetir(vezes=3), roda a fábrica e devolve um decorador. A segunda, (ola), aplica esse decorador à função ola. Se você consegue ler essa linha com naturalidade, o resto é só montar as três funções aninhadas na ordem certa.

Montando a fábrica de três níveis

Agora o código completo. São três funções, uma dentro da outra. A mais externa, repetir, recebe a configuração (vezes) e existe só para devolver o decorador. A do meio, decorador, é um decorador normal: recebe a função a envolver. A mais interna, wrapper, é onde o trabalho acontece, e ela enxerga tanto vezes (da fábrica) quanto funcao (do decorador), graças à closure. Leia de fora para dentro e depois de dentro para fora, até a lógica assentar.

import functools

def repetir(vezes):
    def decorador(funcao):
        @functools.wraps(funcao)
        def wrapper(*args, **kwargs):
            resultado = None
            for _ in range(vezes):
                resultado = funcao(*args, **kwargs)
            return resultado
        return wrapper
    return decorador

@repetir(vezes=3)
def cumprimentar(nome):
    print(f"Ola, {nome}!")
    return nome

cumprimentar("Ana")
# Ola, Ana!
# Ola, Ana!
# Ola, Ana!

Três níveis: repetir (configuração), decorador (recebe a função), wrapper (executa).

NívelFunçãoO que recebeO que devolve
1 (externo)repetirA configuração (vezes)O decorador
2 (meio)decoradorA função a envolverO wrapper
3 (interno)wrapperOs argumentos da chamadaO resultado da função

Os três níveis de uma fábrica de decoradores e o papel de cada um.

Detalhes que valem lembrar

Dois pontos merecem atenção. Primeiro, o functools.wraps continua indo no wrapper, o nível mais interno, exatamente como no decorador simples. Segundo, existe uma pegadinha clássica: escrever @repetir sem parênteses quando o decorador espera argumentos. Nesse caso, o Python passa a própria função como se fosse o parâmetro vezes, e tudo quebra de um jeito confuso. Se o decorador foi feito para receber configuração, ele sempre é usado com parênteses, mesmo que você aceite os valores padrão: @repetir().

Vale um último enquadramento. A fábrica não é um truque à parte, é o mesmo decorador de sempre com uma camada de configuração por fora. Se um dia você travar, escreva a linha equivalente sem a arroba, como ola = repetir(vezes=3)(ola), e siga as chamadas uma a uma. O padrão de três níveis aparece o tempo todo em bibliotecas reais, de frameworks web a ferramentas de cache, então reconhecê-lo de imediato economiza muito esforço.

Teste rápido

Por que um decorador que recebe argumentos precisa de três níveis de função?

Perguntas frequentes

Preciso sempre usar parênteses em @repetir?
Se o decorador foi construído como fábrica (recebe argumentos), sim, sempre com parênteses, mesmo que vazios como @repetir(). Sem os parênteses, o Python passa a função decorada como se fosse o primeiro argumento de configuração, o que gera um erro difícil de entender. Há técnicas para aceitar as duas formas, mas elas complicam o código.
O que é closure e por que ela importa aqui?
Closure é a capacidade de uma função interna lembrar as variáveis do escopo onde foi criada. No decorador de três níveis, o wrapper precisa enxergar tanto o vezes (definido na fábrica) quanto o funcao (definido no decorador). É a closure que mantém esses valores vivos quando o wrapper roda, tempos depois de as funções externas terem retornado.
Posso passar mais de um argumento para a fábrica?
Sim. A função externa aceita quantos parâmetros você quiser, com valores padrão inclusive. Por exemplo, uma fábrica repetir(vezes=3, silencioso=False) recebe dois. Todos ficam disponíveis para o wrapper por causa da closure. Só mantenha a assinatura clara, de preferência com type hints.
Como escrevo a fábrica sem a sintaxe de arroba?
A forma expandida ajuda a entender: minha_funcao = repetir(vezes=3)(minha_funcao). A primeira chamada roda a fábrica e devolve o decorador; a segunda aplica o decorador à função. É literalmente o que a arroba faz por baixo dos panos.
Dá para combinar wraps com a fábrica sem problema?
Sim, e você deve. O @functools.wraps(funcao) continua indo no wrapper, o nível mais interno, exatamente como no decorador simples. A camada extra da fábrica não muda esse detalhe. Assim a função decorada preserva nome e docstring mesmo com a configuração por fora.

Fontes

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