Menu

Decoradores em Python: @funcoes, argumentos e functools.wraps

O que decoradores Python de fato são, como escrever os seus e os padrões (argumentos, empilhamento, wraps) que fazem valer usar.

Um decorador é uma função que envolve uma função

Essa frase soa abstrata, mas a mecânica é simples. Um decorador recebe uma função, devolve uma função. A função que ele devolve geralmente chama a original, com comportamento extra envolvendo a chamada.

O exemplo mais curto possível:

main.py
Output
Click Run to see the output here.

shout é o decorador. Recebe uma função (greet), constrói uma nova função (wrapper) que chama a original e transforma o resultado em maiúsculas, e retorna. Reatribuir greet = shout(greet) troca a original pela versão envolvida.

Esse padrão de reatribuição é tão comum que o Python deu uma sintaxe dedicada.

O @ é açúcar para uma reatribuição

@nome na linha acima de um def é equivalente a name = name(...) logo depois que a função é definida:

main.py
Output
Click Run to see the output here.

@shout se lê como "aplique o decorador shout a esta função". O Python roda greet = shout(greet) logo depois do def — mesma mecânica, menos digitação.

Quando você vir @nome, substitua mentalmente por funcao = nome(funcao). É tudo o que a sintaxe significa.

Lidando com argumentos

A maioria das funções recebe argumentos. Um decorador utilizável repassa. O idioma é *args, **kwargs — o jeito do Python aceitar qualquer argumento — porque o wrapper não deve se importar com o que a função envolvida espera:

main.py
Output
Click Run to see the output here.

*args captura todos os argumentos posicionais. **kwargs captura todos os argumentos por palavra-chave. O wrapper encaminha tudo para a função envolvida sem alterar, e depois faz qualquer trabalho extra para o qual o decorador existe — aqui, maiúsculas no resultado.

Essa é a forma que a maioria dos decoradores reais assume.

Um exemplo mais útil: timing

Imprime quanto uma função demora:

main.py
Output
Click Run to see the output here.

O padrão — rodar algo antes da chamada, rodar algo depois — é o que a maioria dos decoradores acaba fazendo. Logging, checagens de auth, retries e validação de entrada seguem o mesmo formato.

Preservando a identidade original: functools.wraps

Decorar uma função substitui ela, o que significa que a função envolvida perde os atributos originais __name__ e __doc__:

main.py
Output
Click Run to see the output here.

greet.__name__ agora é "wrapper" e a docstring sumiu. Isso quebra help(), tracebacks e qualquer ferramenta que inspecione a função.

A correção é uma linha: @functools.wraps(func) na função interna copia os metadados.

main.py
Output
Click Run to see the output here.

Sempre adicione @wraps(func) à função interna. Não custa nada e evita sessões de debug surpreendentes depois.

Decoradores com argumentos

Às vezes o próprio decorador precisa de configuração — "tente esta função até 3 vezes", "logue no nível DEBUG". Isso significa mais uma camada de aninhamento: uma função externa que recebe os argumentos e retorna um decorador.

main.py
Output
Click Run to see the output here.

Três camadas parece muita coisa. Leia de fora para dentro:

  1. repeat(times=3) é uma chamada de função. Retorna o decorator.
  2. decorator é o decorador de fato — recebe uma função e retorna uma envolvida.
  3. wrapper é a função envolvida que roda no momento da chamada.

Esse formato alimenta @retry(times=5), @cache(maxsize=100) e decoradores de framework como @app.route("/users"). Uma vez que você vê o padrão de três camadas, a família toda se lê igual.

Empilhando decoradores

Você pode aplicar mais de um decorador a uma função. Eles empilham de baixo para cima — o mais próximo do def roda primeiro:

main.py
Output
Click Run to see the output here.

add_exclaim envolve primeiro, adicionando o !. Depois shout envolve aquilo, transformando tudo em maiúsculas. A saída é HI, ROSA!.

A ordem importa. Inverter a pilha te daria HI, ROSA! com a exclamação adicionada depois do upcase — visualmente idêntico aqui, mas imagine um decorador que formata JSON: rodar antes ou depois de um decorador que loga a entrada pode produzir resultados bem diferentes.

Decoradores embutidos que você vai ver

O Python e sua biblioteca padrão vêm com alguns decoradores que você vai encontrar em código real:

main.py
Output
Click Run to see the output here.
  • @property transforma um método num atributo computado.
  • @staticmethod marca um método que não usa self ou cls.
  • @classmethod recebe a classe como cls em vez de uma instância — ótimo para construtores alternativos.
  • @functools.lru_cache memoiza resultados, então chamadas repetidas com os mesmos argumentos batem num cache.

Decoradores de framework (@app.route, @pytest.fixture, @dataclass) seguem a mesma maquinaria. Nada de especial — só funções que envolvem funções.

Quando escrever um, quando não

Escreva um decorador quando quer aplicar o mesmo comportamento a várias funções — timing, logging, retries, checagens de autorização. O ponto é que o comportamento fica fora do corpo da função.

Pule o decorador quando:

  • O comportamento pertence a uma função específica. Coloque na função.
  • Você só quer para teste. Uma fixture ou um parâmetro é mais claro.
  • Você está tentado a empilhar quatro ou cinco. Nesse ponto, o fluxo de controle está escondido na cadeia de decoradores — um leitor tem que desenrolar cada camada para ver o que de fato roda. Uma função auxiliar direta pode ler melhor.

Decoradores são uma ferramenta afiada. Usados bem, mantêm o código DRY e a intenção óbvia. Usados mal, escondem o que o programa está fazendo. Incline-se para "óbvio" ao decidir se vai usar um.

Próxima: type hints

Decoradores são um lugar comum para encontrar type hints na prática — as funções wrapper muitas vezes anotam suas assinaturas. Type hints são uma feature pequena que compensa rápido, e vêm na sequência.

Perguntas frequentes

O que é um decorador em Python?

Um decorador é uma função que recebe outra função e retorna uma nova função — geralmente uma que envolve a original com comportamento extra. Você aplica um com @nome_do_decorador na linha acima de um def. A sintaxe @ é atalho para func = nome_do_decorador(func).

Para que decoradores são usados em Python?

Adicionar comportamento ao redor de uma função sem editar o corpo dela — logging, timing, cache, checagens de autenticação, validação de entrada, retries. Frameworks usam muito: @app.route(...) no Flask, @pytest.fixture no pytest, @property e @staticmethod embutidos.

Posso escrever meu próprio decorador?

Sim. Um decorador é só uma função que recebe uma função e retorna uma função. A maioria dos decoradores customizados envolve a chamada original numa pequena função interna que faz algo antes, depois ou ao redor dela. Use functools.wraps na função interna para preservar o nome e a docstring da função original.

Aprenda a programar com o Coddy

COMEÇAR