Python

Decoradores e Metaprogramação Já leu

8 min de leitura

Decoradores e Metaprogramação
Decoradores são um dos recursos mais elegantes do Python. Eles permitem modificar ou estender o comportamento de funções e classes sem alte

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.wraps para preservar metadados da função decorada
  • Decoradores com parâmetros exigem três níveis de função aninhada
  • @singleton, @retry, @medir_tempo são padrões clássicos implementados como decoradores
  • __getattr__ e __setattr__ interceptam acesso a atributos — base da metaprogramação
  • @dataclass usa 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.
Comentários

Mais em Python

Listas: criação, manipulação e métodos
Listas: criação, manipulação e métodos

Listas s&atilde;o a estrutura de dados mais utilizada em Python. Elas permite...

Tratamento de Exceções e Erros
Tratamento de Exceções e Erros

Todo programa que interage com o mundo real encontra situa&ccedil;&otilde;es...

A História do Python e os Primeiros Passos
A História do Python e os Primeiros Passos

Antes de escrever a primeira linha de c&oacute;digo, vale a pena entender de...