Módulo 12 - Decoradores

Decorador para qualquer função com args e kwargs

11 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 o wrapper original quebra com funções que recebem argumentos.
  • Usar *args e **kwargs no wrapper para aceitar qualquer assinatura.
  • Repassar os argumentos para a função original sem perder nenhum.
  • Não esquecer de devolver o resultado da função com return.

Por que o wrapper precisa de args e kwargs

O decorador da aula anterior tinha um limite escondido. O wrapper foi escrito como def wrapper(), sem parâmetros, e por dentro chamava func() também sem argumentos. Isso funciona para dizer_ola, que não recebe nada. Mas tente decorar uma função que soma dois números e tudo desmorona: quando você chama a versão decorada passando os dois números, quem recebe esses argumentos é o wrapper, que não os espera. O Python reclama na hora que o wrapper não aceita argumentos. Um decorador de verdade precisa servir para qualquer função, e é aí que entram *args e **kwargs.

def com_moldura(func):
    def wrapper():          # sem parâmetros: problema!
        print("--- antes ---")
        func()
        print("--- depois ---")
    return wrapper

@com_moldura
def somar(a, b):
    return a + b

somar(2, 3)
# TypeError: wrapper() takes 0 positional arguments but 2 were given

O wrapper sem parâmetros não sabe o que fazer com os argumentos de somar.

O wrapper que aceita qualquer função

A solução é fazer o wrapper aceitar tudo e repassar tudo. Você já viu *args e **kwargs no módulo de funções avançadas: *args recolhe os argumentos posicionais numa tupla e **kwargs recolhe os nomeados num dicionário. Escrevendo def wrapper(*args, **kwargs), o wrapper aceita qualquer chamada. Dentro dele, chamamos a função original com func(*args, **kwargs), o que desempacota tudo de volta e entrega os argumentos exatos que a função esperava. Falta um detalhe fácil de esquecer e caro de depurar: devolver o resultado. Se a função original retorna um valor, o wrapper precisa fazer return func(...); caso contrário, a versão decorada passa a devolver None em silêncio.

def com_moldura(func):
    def wrapper(*args, **kwargs):
        print("--- antes ---")
        resultado = func(*args, **kwargs)   # repassa tudo
        print("--- depois ---")
        return resultado                    # devolve o valor original
    return wrapper

@com_moldura
def somar(a, b):
    return a + b

@com_moldura
def cumprimentar(nome, saudacao="Olá"):
    return f"{saudacao}, {nome}!"

print(somar(2, 3))
# --- antes ---
# --- depois ---
# 5
print(cumprimentar("Ana", saudacao="Oi"))
# --- antes ---
# --- depois ---
# Oi, Ana!

Com *args e **kwargs, o mesmo decorador serve para somar e para cumprimentar.

Verificando o padrão universal

A partir de agora, todo wrapper que você escrever nasce com a assinatura def wrapper(*args, **kwargs) e termina com return func(*args, **kwargs) em algum ponto. Esse é o formato genérico que funciona com qualquer função, receba ela zero, dois ou dez argumentos, posicionais ou nomeados. Guarde os dois cuidados: aceitar tudo na entrada com *args e **kwargs, e devolver o resultado no fim. Com isso resolvido, o decorador já é utilizável de verdade. Ainda sobra um detalhe cosmético importante, que a próxima aula ataca: o nome da função decorada passa a mostrar wrapper, e não o nome original.

Teste rápido

O wrapper chama func(*args, **kwargs) mas não usa return. O que acontece com uma função decorada que deveria retornar um valor?

Perguntas frequentes

Preciso sempre usar *args e **kwargs juntos?
Como padrão, sim. Usar os dois juntos no wrapper garante que ele funcione com qualquer função, aceite ela argumentos posicionais, nomeados, ambos ou nenhum. Escrever apenas *args deixaria de fora os argumentos por nome. Como regra, adote def wrapper(*args, **kwargs) para todo decorador de uso geral.
Qual a diferença entre *args na definição e na chamada?
Na definição do wrapper, *args recolhe os argumentos numa tupla. Na chamada func(*args), o asterisco faz o contrário: desempacota a tupla de volta em argumentos separados. É o mesmo símbolo com papéis espelhados, empacotar ao receber e desempacotar ao repassar.
E se a função original não retorna nada?
Não tem problema usar return func(*args, **kwargs) mesmo assim. Se a função original não devolve valor, ela devolve None por padrão, e o wrapper repassa esse None. Ou seja, colocar o return sempre é seguro e evita o esquecimento que faz sumir resultados.
Posso guardar o resultado numa variável antes de retornar?
Sim, e muitas vezes você vai querer. Escrever resultado = func(*args, **kwargs), fazer algo depois (como medir tempo ou registrar) e então return resultado é o padrão quando o wrapper precisa agir também após a chamada. Foi exatamente o que fizemos no exemplo com moldura.
O wrapper precisa ter os mesmos nomes de parâmetros da função original?
Não. É justamente essa a vantagem de *args e **kwargs: o wrapper não precisa saber os nomes nem a quantidade de argumentos. Ele aceita tudo genericamente e repassa intacto. Assim, o mesmo decorador serve para funções com assinaturas completamente diferentes.
Isso funciona com métodos de classe também?
Funciona. Como o self de um método é apenas o primeiro argumento posicional, ele entra em *args normalmente e é repassado sem tratamento especial. O padrão def wrapper(*args, **kwargs) mais func(*args, **kwargs) é robusto o bastante para métodos e funções comuns.

Fontes

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