Decoradores são um dos recursos mais elegantes do Python. Eles permitem modificar ou estender o comportamento de funções e classes sem alterar seu código-fonte — adicionando funcionalidades de forma declarativa e reutilizável. São amplamente usados em frameworks como Flask, FastAPI e Django, e entendê-los aprofunda significativamente sua compreensão da linguagem.
Funções são Objetos de Primeira Classe
Para entender decoradores, primeiro precisamos confirmar algo visto no Artigo 06: funções em Python são objetos — podem ser atribuídas a variáveis, passadas como argumentos e retornadas por outras funções:
def saudar(nome):
return f"Olá, {nome}!"
# Atribuindo a uma variável
cumprimentar = saudar
print(cumprimentar("Ana")) # Olá, Ana!
# Passando como argumento
def executar(func, valor):
return func(valor)
print(executar(saudar, "Bruno")) # Olá, Bruno!
# Retornando de outra função
def criar_saudacao(prefixo):
def saudar(nome):
return f"{prefixo}, {nome}!"
return saudar
ola = criar_saudacao("Olá")
bom_dia = criar_saudacao("Bom dia")
print(ola("Carla")) # Olá, Carla!
print(bom_dia("Diego")) # Bom dia, Diego!
Closures
Uma closure é uma função que captura variáveis do escopo onde foi criada, mesmo após esse escopo ter encerrado:
def contador(inicio=0):
count = [inicio] # lista para permitir mutação no escopo interno
def incrementar():
count[0] += 1
return count[0]
def resetar():
count[0] = inicio
def valor_atual():
return count[0]
return incrementar, resetar, valor_atual
inc, reset, valor = contador(10)
print(inc()) # 11
print(inc()) # 12
print(inc()) # 13
print(valor()) # 13
reset()
print(valor()) # 10
O que é um Decorador?
Um decorador é uma função que recebe outra função, adiciona comportamento e retorna uma nova função:
def meu_decorador(func):
def wrapper(*args, **kwargs):
print("Antes da função")
resultado = func(*args, **kwargs)
print("Depois da função")
return resultado
return wrapper
def saudar(nome):
print(f"Olá, {nome}!")
saudar_decorada = meu_decorador(saudar)
saudar_decorada("Ana")
# Antes da função
# Olá, Ana!
# Depois da função
A sintaxe @ é apenas açúcar sintático para isso:
@meu_decorador
def saudar(nome):
print(f"Olá, {nome}!")
# Equivalente a: saudar = meu_decorador(saudar)
saudar("Bruno")
functools.wraps
Sem wraps, o decorador esconde o nome e a docstring da função original:
from functools import wraps
def meu_decorador(func):
@wraps(func) # preserva metadados da função original
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@meu_decorador
def saudar(nome):
"""Saúda uma pessoa pelo nome."""
return f"Olá, {nome}!"
print(saudar.__name__) # saudar (não wrapper)
print(saudar.__doc__) # Saúda uma pessoa pelo nome.
Sempre use @wraps ao escrever decoradores.
Decoradores Práticos
Medição de tempo
import time
from functools import wraps
def medir_tempo(func):
@wraps(func)
def wrapper(*args, **kwargs):
inicio = time.perf_counter()
resultado = func(*args, **kwargs)
fim = time.perf_counter()
print(f"[{func.__name__}] executou em {fim - inicio:.4f}s")
return resultado
return wrapper
@medir_tempo
def processar_dados(n):
return sum(i ** 2 for i in range(n))
print(processar_dados(1_000_000))
# [processar_dados] executou em 0.1823s
# 333332833333500000
Cache simples
from functools import wraps
def cache(func):
_cache = {}
@wraps(func)
def wrapper(*args):
if args not in _cache:
_cache[args] = func(*args)
return _cache[args]
return wrapper
@cache
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # instantâneo
Controle de acesso
from functools import wraps
def requer_autenticacao(func):
@wraps(func)
def wrapper(usuario, *args, **kwargs):
if not usuario.get("autenticado"):
raise PermissionError("Acesso negado. Faça login.")
return func(usuario, *args, **kwargs)
return wrapper
def requer_admin(func):
@wraps(func)
def wrapper(usuario, *args, **kwargs):
if usuario.get("nivel") != "admin":
raise PermissionError("Requer privilégios de administrador.")
return func(usuario, *args, **kwargs)
return wrapper
@requer_autenticacao
@requer_admin
def deletar_usuario(usuario, alvo_id):
print(f"{usuario['nome']} deletou o usuário {alvo_id}.")
admin = {"nome": "Ana", "autenticado": True, "nivel": "admin"}
comum = {"nome": "Bruno", "autenticado": True, "nivel": "usuario"}
deletar_usuario(admin, 42) # Ana deletou o usuário 42.
try:
deletar_usuario(comum, 42)
except PermissionError as e:
print(e) # Requer privilégios de administrador.
Decoradores empilhados são aplicados de baixo para cima: primeiro requer_admin, depois requer_autenticacao.
Decoradores com Parâmetros
Para criar decoradores que aceitam argumentos, adicione mais um nível de função:
from functools import wraps
import time
def retry(tentativas=3, espera=1.0):
"""Tenta executar a função até n vezes em caso de exceção."""
def decorador(func):
@wraps(func)
def wrapper(*args, **kwargs):
for tentativa in range(1, tentativas + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Tentativa {tentativa}/{tentativas} falhou: {e}")
if tentativa < tentativas:
time.sleep(espera)
raise RuntimeError(f"{func.__name__} falhou após {tentativas} tentativas.")
return wrapper
return decorador
@retry(tentativas=3, espera=0.5)
def conectar_banco():
import random
if random.random() < 0.7: # 70% de chance de falhar
raise ConnectionError("Timeout na conexão.")
return "Conectado!"
try:
print(conectar_banco())
except RuntimeError as e:
print(e)
Decoradores de Classe
Decoradores também funcionam em classes:
def singleton(cls):
"""Garante que apenas uma instância da classe seja criada."""
instancias = {}
@wraps(cls)
def obter_instancia(*args, **kwargs):
if cls not in instancias:
instancias[cls] = cls(*args, **kwargs)
return instancias[cls]
return obter_instancia
@singleton
class ConfiguracaoApp:
def __init__(self):
self.tema = "escuro"
self.idioma = "pt-BR"
self.debug = False
config1 = ConfiguracaoApp()
config2 = ConfiguracaoApp()
config1.tema = "claro"
print(config2.tema) # claro — mesmo objeto
print(config1 is config2) # True
Introdução à Metaprogramação: getattr e setattr
Metaprogramação é escrever código que manipula código. Python expõe ganchos para interceptar operações comuns:
class Configuracao:
"""Classe que registra todo acesso e modificação de atributos."""
def __init__(self, **kwargs):
object.__setattr__(self, "_dados", {})
object.__setattr__(self, "_log", [])
for k, v in kwargs.items():
setattr(self, k, v)
def __setattr__(self, nome, valor):
self._log.append(f"SET {nome} = {valor!r}")
self._dados[nome] = valor
def __getattr__(self, nome):
if nome in self._dados:
self._log.append(f"GET {nome}")
return self._dados[nome]
raise AttributeError(f"Atributo '{nome}' não encontrado.")
def historico(self):
return self._log.copy()
cfg = Configuracao(host="localhost", porta=5432)
cfg.debug = True
_ = cfg.host
for entrada in cfg.historico():
print(entrada)
# SET host = 'localhost'
# SET porta = 5432
# SET debug = True
# GET host
dataclasses: Metaprogramação Embutida
O módulo dataclasses usa metaprogramação para gerar automaticamente __init__, __repr__, __eq__ e outros métodos:
from dataclasses import dataclass, field
from typing import List
@dataclass
class Aluno:
nome: str
nota: float
turma: str = "A"
tags: List[str] = field(default_factory=list)
def aprovado(self):
return self.nota >= 6.0
def __post_init__(self):
if not 0 <= self.nota <= 10:
raise ValueError(f"Nota inválida: {self.nota}")
@dataclass(order=True, frozen=True)
class Ponto:
x: float
y: float
def distancia_origem(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
a1 = Aluno("Ana", 9.5)
a2 = Aluno("Bruno", 7.0, turma="B", tags=["monitor", "destaque"])
print(a1) # Aluno(nome='Ana', nota=9.5, turma='A', tags=[])
print(a1.aprovado()) # True
print(a2)
p1 = Ponto(3.0, 4.0)
p2 = Ponto(1.0, 1.0)
print(p1.distancia_origem()) # 5.0
print(p1 > p2) # True — order=True gera comparadores
# p1.x = 5 # FrozenInstanceError — frozen=True impede modificação
Resumo
- Funções são objetos — podem ser passadas, retornadas e atribuídas a variáveis
- Closures capturam variáveis do escopo externo e as mantêm vivas
- Decoradores envolvem funções para adicionar comportamento sem modificar o código original
- Sempre use
@functools.wrapspara preservar metadados da função decorada - Decoradores com parâmetros exigem três níveis de função aninhada
@singleton,@retry,@medir_temposão padrões clássicos implementados como decoradores__getattr__e__setattr__interceptam acesso a atributos — base da metaprogramação@dataclassusa metaprogramação para eliminar código boilerplate em classes de dados
Referências e Leituras Complementares
- functools.wraps e lru_cache — https://docs.python.org/3/library/functools.html
- dataclasses (PEP 557) — https://docs.python.org/3/library/dataclasses.html
- Modelo de dados — métodos especiais — https://docs.python.org/3/reference/datamodel.html
- PEP 318 — decoradores de funções — https://peps.python.org/pep-0318/
- RAMALHO, Luciano. Fluent Python. 2. ed. O'Reilly Media, 2022. Cap. 9 e 24 — decoradores e metaprogramação em profundidade.
- BEAZLEY, David; JONES, Brian K. Python Cookbook. 3. ed. O'Reilly Media, 2013. Cap. 9 — receitas avançadas com decoradores e metaprogramação.
- MARTELLI, Alex et al. Python in a Nutshell. 4. ed. O'Reilly Media, 2023. Cap. 4 — modelo de objetos e introspecção.