<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 "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.</p>
<p>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.</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"'{func.__name__}' levou {fim - inicio:.4f} segundos")
return resultado
return wrapper
@cronometro
def calcular_fibonacci(n):
if n <= 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"Primeiro argumento deve ser número, recebeu {type(args[0])}")
if args and args[0] <= 0:
raise ValueError("Primeiro argumento deve ser positivo")
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"[LOG] Chamando {func.__name__} com args={args}, kwargs={kwargs}")
resultado = func(args, *kwargs)
print(f"[LOG] {func.__name__} retornou {resultado}")
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"[TEMPO] {func.__name__} executou em {tempo:.6f}s")
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 = {"chamadas": 0}
def contador(func):
@wraps(func)
def wrapper(args, *kwargs):
contexto["chamadas"] += 1
print(f"Chamada #{contexto['chamadas']}")
return func(args, *kwargs)
return wrapper
return contador
contador_global = criar_contexto_decorador()
@contador_global
def saudar(nome):
print(f"Olá, {nome}!")
saudar("Alice") # Chamada #1
saudar("Bob") # 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"Olá, {nome}!"
print(cumprimento("Maria"))
Saída: ['Olá, Maria!', 'Olá, Maria!', 'Olá, Maria!']</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"[ERRO] Tentativa {tentativa}/{tentativas} falhou: {e}")
if tentativa < tentativas:
print(f"[AGUARDANDO] {aguardar}s antes da próxima tentativa...")
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() < 0.7:
raise ConnectionError("Falha na conexão")
return f"Sucesso com {numero}"
try:
print(operacao_instavel(42))
except Exception as e:
print(f"Falha final: {e}")</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="DEBUG"):
def decorator(func):
@wraps(func)
def wrapper(args, *kwargs):
print(f"[{prefixo}] Executando {func.__name__}")
resultado = func(args, *kwargs)
print(f"[{prefixo}] Resultado: {resultado}")
return resultado
return wrapper
return decorator
@debug() # Usa prefixo padrão
def funcao_um():
return "Um"
@debug(prefixo="INFO") # Customiza o prefixo
def funcao_dois():
return "Dois"
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><!-- FIM --></p>