Python

Dominando Decorators em Python: Criando, Empilhando e Parametrizando em Projetos Reais

11 min de leitura

Dominando Decorators em Python: Criando, Empilhando e Parametrizando em Projetos Reais

Entendendo Decorators: O Conceito Fundamental Um decorator em Python é uma função que recebe outra função como argumento, adiciona alguma funcionalidade a ela sem modificar sua estrutura original, e retorna uma nova função com essa funcionalidade estendida. Pense em um decorator como um "embrulho" que você coloca ao redor de uma função — o conteúdo interno continua o mesmo, mas agora há camadas adicionais que podem fazer algo antes, depois ou até mesmo durante a execução. A razão pela qual decorators são tão poderosos é que eles respeitam o princípio DRY (Don't Repeat Yourself) e permitem separação de responsabilidades. Em vez de copiar código de validação, logging ou controle de tempo em múltiplas funções, você escreve isso uma única vez em um decorator e o reutiliza. Isso também torna seu código mais legível e fácil de manter. Vamos começar com um exemplo prático e simples. Imagine que você quer saber quanto tempo cada função leva para executar: python import time

<h2>Entendendo Decorators: O Conceito Fundamental</h2>

<p>Um decorator em Python é uma função que recebe outra função como argumento, adiciona alguma funcionalidade a ela sem modificar sua estrutura original, e retorna uma nova função com essa funcionalidade estendida. Pense em um decorator como um &quot;embrulho&quot; que você coloca ao redor de uma função — o conteúdo interno continua o mesmo, mas agora há camadas adicionais que podem fazer algo antes, depois ou até mesmo durante a execução.</p>

<p>A razão pela qual decorators são tão poderosos é que eles respeitam o princípio DRY (Don&#039;t Repeat Yourself) e permitem separação de responsabilidades. Em vez de copiar código de validação, logging ou controle de tempo em múltiplas funções, você escreve isso uma única vez em um decorator e o reutiliza. Isso também torna seu código mais legível e fácil de manter.</p>

<p>Vamos começar com um exemplo prático e simples. Imagine que você quer saber quanto tempo cada função leva para executar:</p>

<pre><code class="language-python">import time

from functools import wraps

def cronometro(func):

@wraps(func)

def wrapper(args, *kwargs):

inicio = time.time()

resultado = func(args, *kwargs)

fim = time.time()

print(f&quot;&#039;{func.__name__}&#039; levou {fim - inicio:.4f} segundos&quot;)

return resultado

return wrapper

@cronometro

def calcular_fibonacci(n):

if n &lt;= 1:

return n

return calcular_fibonacci(n-1) + calcular_fibonacci(n-2)

calcular_fibonacci(10)</code></pre>

<p>Neste exemplo, o decorator <code>cronometro</code> envolve qualquer função e mede seu tempo de execução. O <code>@wraps</code> é importante — ele preserva o nome e a documentação originais da função decorada, evitando que você perca metadados.</p>

<h3>Por que usar <code>@wraps</code>?</h3>

<p>Sem <code>@wraps</code>, a função decorada perde seu <code>__name__</code> original e documentação. O decorator <code>@wraps</code> (vindo de <code>functools</code>) copia esses metadados para a função wrapper, mantendo a identidade original intacta. Isso é especialmente útil em debugging e documentação automática.</p>

<h2>Criando Seu Primeiro Decorator Funcional</h2>

<p>Para dominar decorators, você precisa entender o fluxo de execução em camadas. Um decorator é, fundamentalmente, uma função que retorna outra função. Quando você usa <code>@nome_decorator</code>, Python automaticamente transforma <code>funcao()</code> em <code>decorator(funcao)()</code>.</p>

<p>Vamos criar um decorator prático que valida se um parâmetro é um número positivo:</p>

<pre><code class="language-python">from functools import wraps

def valida_positivo(func):

@wraps(func)

def wrapper(args, *kwargs):

Verifica se o primeiro argumento é positivo

if args and not isinstance(args[0], (int, float)):

raise TypeError(f&quot;Primeiro argumento deve ser número, recebeu {type(args[0])}&quot;)

if args and args[0] &lt;= 0:

raise ValueError(&quot;Primeiro argumento deve ser positivo&quot;)

return func(args, *kwargs)

return wrapper

@valida_positivo

def calcular_raiz_quadrada(numero):

return numero ** 0.5

print(calcular_raiz_quadrada(16)) # Saída: 4.0

print(calcular_raiz_quadrada(-5)) # Levanta ValueError</code></pre>

<p>Este decorator valida a entrada antes de a função ser executada. É uma forma elegante de adicionar guardrails sem poluir a função principal com código de validação. Se você tiver dez funções que precisam dessa validação, aplica o decorator em cada uma — simples e consistente.</p>

<h3>Anatomia de um Decorator</h3>

<p>O padrão básico sempre segue esta estrutura:</p>

<pre><code class="language-python">def meu_decorator(func):

@wraps(func)

def wrapper(args, *kwargs):

Lógica ANTES da função

resultado = func(args, *kwargs)

Lógica DEPOIS da função

return resultado

return wrapper</code></pre>

<p>A <code><em>args</code> captura argumentos posicionais e <code></em>*kwargs</code> captura argumentos nomeados. Isso garante que seu decorator funcione com qualquer assinatura de função.</p>

<h2>Empilhando Decorators: Composição em Ação</h2>

<p>Um dos recursos mais poderosos de Python é a capacidade de aplicar múltiplos decorators em uma única função. Quando você empilha decorators, eles são aplicados de baixo para cima — ou seja, o decorator mais próximo da função executa por último na cadeia, mas sua função wrapper executa primeiro.</p>

<p>Vamos criar dois decorators práticos e vê-los trabalhando juntos:</p>

<pre><code class="language-python">from functools import wraps

import time

def logger(func):

@wraps(func)

def wrapper(args, *kwargs):

print(f&quot;[LOG] Chamando {func.__name__} com args={args}, kwargs={kwargs}&quot;)

resultado = func(args, *kwargs)

print(f&quot;[LOG] {func.__name__} retornou {resultado}&quot;)

return resultado

return wrapper

def tempo_execucao(func):

@wraps(func)

def wrapper(args, *kwargs):

inicio = time.time()

resultado = func(args, *kwargs)

tempo = time.time() - inicio

print(f&quot;[TEMPO] {func.__name__} executou em {tempo:.6f}s&quot;)

return resultado

return wrapper

@logger

@tempo_execucao

def multiplicar(a, b):

time.sleep(0.1)

return a * b

multiplicar(3, 5)</code></pre>

<p><strong>Ordem de execução:</strong> Quando você chama <code>multiplicar(3, 5)</code>, a execução ocorre assim:</p>

<ol>

<li><code>logger</code> wrapper começa (imprime o log de entrada)</li>

<li><code>tempo_execucao</code> wrapper começa (inicia o cronômetro)</li>

<li>A função original <code>multiplicar</code> executa</li>

<li><code>tempo_execucao</code> wrapper termina (imprime o tempo)</li>

<li><code>logger</code> wrapper termina (imprime o log de saída)</li>

</ol>

<p>A ordem importa! Se você inverter os decorators (<code>@tempo_execucao</code> em cima de <code>@logger</code>), o log será envolvido pelo cronômetro, o que mudará o que está sendo medido. Teste ambas as formas e você verá a diferença.</p>

<h3>Decorators com Estado Compartilhado</h3>

<p>Às vezes, você quer que múltiplos decorators compartilhem informação. Embora raro, é possível:</p>

<pre><code class="language-python">def criar_contexto_decorador():

contexto = {&quot;chamadas&quot;: 0}

def contador(func):

@wraps(func)

def wrapper(args, *kwargs):

contexto[&quot;chamadas&quot;] += 1

print(f&quot;Chamada #{contexto[&#039;chamadas&#039;]}&quot;)

return func(args, *kwargs)

return wrapper

return contador

contador_global = criar_contexto_decorador()

@contador_global

def saudar(nome):

print(f&quot;Olá, {nome}!&quot;)

saudar(&quot;Alice&quot;) # Chamada #1

saudar(&quot;Bob&quot;) # Chamada #2</code></pre>

<h2>Parametrizando Decorators: Flexibilidade Total</h2>

<p>Decorators parametrizados permitem que você customize seu comportamento no momento da aplicação. Em vez de <code>@decorator</code>, você usa <code>@decorator(parametro)</code>. Isso adiciona uma camada extra de nesting, mas oferece flexibilidade imensa.</p>

<p>A estrutura de um decorator parametrizado é: uma função que retorna um decorator que retorna um wrapper:</p>

<pre><code class="language-python">from functools import wraps

def repetir(vezes):

def decorator(func):

@wraps(func)

def wrapper(args, *kwargs):

resultados = []

for i in range(vezes):

resultado = func(args, *kwargs)

resultados.append(resultado)

return resultados

return wrapper

return decorator

@repetir(vezes=3)

def cumprimento(nome):

return f&quot;Olá, {nome}!&quot;

print(cumprimento(&quot;Maria&quot;))

Saída: [&#039;Olá, Maria!&#039;, &#039;Olá, Maria!&#039;, &#039;Olá, Maria!&#039;]</code></pre>

<p>Neste exemplo, <code>@repetir(vezes=3)</code> cria um decorator que executa a função três vezes e retorna uma lista com todos os resultados. A parametrização torna o decorator reutilizável em contextos diferentes — você pode usar <code>@repetir(vezes=5)</code> em outra função.</p>

<h3>Exemplo Prático: Decorator com Múltiplos Parâmetros</h3>

<p>Vamos criar um decorator mais complexo que combina logging, retry e validação:</p>

<pre><code class="language-python">from functools import wraps

import time

def resiliente(tentativas=3, aguardar=1, log_erros=True):

def decorator(func):

@wraps(func)

def wrapper(args, *kwargs):

ultima_excecao = None

for tentativa in range(1, tentativas + 1):

try:

return func(args, *kwargs)

except Exception as e:

ultima_excecao = e

if log_erros:

print(f&quot;[ERRO] Tentativa {tentativa}/{tentativas} falhou: {e}&quot;)

if tentativa &lt; tentativas:

print(f&quot;[AGUARDANDO] {aguardar}s antes da próxima tentativa...&quot;)

time.sleep(aguardar)

raise ultima_excecao

return wrapper

return decorator

@resiliente(tentativas=3, aguardar=0.5, log_erros=True)

def operacao_instavel(numero):

import random

if random.random() &lt; 0.7:

raise ConnectionError(&quot;Falha na conexão&quot;)

return f&quot;Sucesso com {numero}&quot;

try:

print(operacao_instavel(42))

except Exception as e:

print(f&quot;Falha final: {e}&quot;)</code></pre>

<p>Este decorator é especialmente útil para operações que podem falhar temporariamente (chamadas a APIs, leitura de banco de dados) — ele tenta de novo automaticamente.</p>

<h3>Decorators Parametrizados com Valores Padrão</h3>

<p>Você pode tornar seus parâmetros opcionais:</p>

<pre><code class="language-python">from functools import wraps

def debug(prefixo=&quot;DEBUG&quot;):

def decorator(func):

@wraps(func)

def wrapper(args, *kwargs):

print(f&quot;[{prefixo}] Executando {func.__name__}&quot;)

resultado = func(args, *kwargs)

print(f&quot;[{prefixo}] Resultado: {resultado}&quot;)

return resultado

return wrapper

return decorator

@debug() # Usa prefixo padrão

def funcao_um():

return &quot;Um&quot;

@debug(prefixo=&quot;INFO&quot;) # Customiza o prefixo

def funcao_dois():

return &quot;Dois&quot;

funcao_um()

funcao_dois()</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>decorators são uma ferramenta de composição funcional</strong> que permite adicionar comportamento a funções sem modificar seu código original — e que isso respeita o princípio de responsabilidade única. Em segundo lugar, <strong>empilhar decorators oferece elegância e modularidade</strong>, permitindo que você combine funcionalidades de forma clara e legível, desde que entenda a ordem de execução. Por fim, <strong>parametrizar decorators transforma-os em ferramentas altamente reutilizáveis</strong>, capazes de se adaptar a contextos diferentes sem duplicação de código.</p>

<p>A verdadeira maestria vem com a prática: escreva decorators para logging, validação, cache e tratamento de erros em seus projetos reais. Você descobrirá que muitos problemas de engenharia de código têm uma solução elegante através de decorators bem projetados.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.python.org/dev/peps/pep-0318/" target="_blank" rel="noopener noreferrer">PEP 318 - Decorators for Functions and Methods</a></li>

<li><a href="https://docs.python.org/3/library/functools.html" target="_blank" rel="noopener noreferrer">Python <code>functools</code> Documentation</a></li>

<li><a href="https://realpython.com/primer-on-python-decorators/" target="_blank" rel="noopener noreferrer">Real Python - Decorators</a></li>

<li><a href="https://www.oreilly.com/library/view/fluent-python-2nd/9781491946237/" target="_blank" rel="noopener noreferrer">Fluent Python - Luciano Ramalho (Capítulo sobre Decorators)</a></li>

<li><a href="https://docs.python.org/3/library/functools.html#functools.wraps" target="_blank" rel="noopener noreferrer">Python functools.wraps</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

Guia Completo de Variáveis de Ambiente em Python: python-dotenv e pydantic-settings
Guia Completo de Variáveis de Ambiente em Python: python-dotenv e pydantic-settings

Por que Variáveis de Ambiente Importam Variáveis de ambiente são valores conf...

Django REST Framework: Serializers, ViewSets e Autenticação: Do Básico ao Avançado
Django REST Framework: Serializers, ViewSets e Autenticação: Do Básico ao Avançado

Serializers: A Base da Transformação de Dados Um serializer no Django REST Fr...

Dominando Testes de APIs Python: pytest com httpx e TestClient do FastAPI em Projetos Reais
Dominando Testes de APIs Python: pytest com httpx e TestClient do FastAPI em Projetos Reais

Por que Testar APIs com Python? Testar uma API é tão importante quanto desenv...