Decorators e Generators são recursos avançados do Python que separam programadores intermediários dos profissionais. Decorators permitem modificar o comportamento de funções de forma elegante, enquanto Generators criam iteradores eficientes em memória. Neste guia completo, você dominará ambos os conceitos com exemplos práticos do mundo real.
🎨 O Que São Decorators?
Decorators são funções que modificam o comportamento de outras funções sem alterar seu código. Pense neles como "wrappers" ou "embalagens" que adicionam funcionalidades extras.
# Decorator simples
def meu_decorator(funcao):
def wrapper():
print("🚀 Antes da função")
funcao()
print("✅ Depois da função")
return wrapper
@meu_decorator
def saudacao():
print("Olá, Universo Python!")
# Chamar a função decorada
saudacao()
# Output:
# 🚀 Antes da função
# Olá, Universo Python!
# ✅ Depois da função
O símbolo @ é apenas "açúcar sintático" para:
# Isso:
@meu_decorator
def saudacao():
print("Olá!")
# É equivalente a:
def saudacao():
print("Olá!")
saudacao = meu_decorator(saudacao)
📝 Criando Decorators
Decorator com Argumentos da Função
def log_chamada(funcao):
"""Registra quando uma função é chamada"""
def wrapper(*args, **kwargs):
print(f"📞 Chamando: {funcao.__name__}")
print(f" Args: {args}")
print(f" Kwargs: {kwargs}")
resultado = funcao(*args, **kwargs)
print(f" Retorno: {resultado}")
return resultado
return wrapper
@log_chamada
def somar(a, b):
return a + b
@log_chamada
def saudacao(nome, saudacao="Olá"):
return f"{saudacao}, {nome}!"
print(somar(10, 5))
print(saudacao("Python", saudacao="Bem-vindo"))
Preservando Metadados com functools.wraps
from functools import wraps
def meu_decorator(funcao):
@wraps(funcao) # Preserva __name__, __doc__, etc.
def wrapper(*args, **kwargs):
"""Wrapper interno"""
return funcao(*args, **kwargs)
return wrapper
@meu_decorator
def minha_funcao():
"""Documentação original"""
pass
print(minha_funcao.__name__) # minha_funcao (não 'wrapper')
print(minha_funcao.__doc__) # Documentação original
Decorator com Parâmetros
from functools import wraps
def repetir(vezes):
"""Decorator que repete a função N vezes"""
def decorator(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
resultados = []
for _ in range(vezes):
resultado = funcao(*args, **kwargs)
resultados.append(resultado)
return resultados
return wrapper
return decorator
@repetir(vezes=3)
def dizer_oi():
print("Oi!")
return "disse oi"
resultados = dizer_oi()
# Output: Oi! (3 vezes)
print(resultados) # ['disse oi', 'disse oi', 'disse oi']
⚡ Decorators Úteis na Prática
Timer - Medir Tempo de Execução
import time
from functools import wraps
def timer(funcao):
"""Mede o tempo de execução de uma função"""
@wraps(funcao)
def wrapper(*args, **kwargs):
inicio = time.perf_counter()
resultado = funcao(*args, **kwargs)
fim = time.perf_counter()
duracao = fim - inicio
print(f"⏱️ {funcao.__name__} executou em {duracao:.4f}s")
return resultado
return wrapper
@timer
def processar_dados():
time.sleep(1) # Simular processamento
return "Dados processados"
processar_dados() # ⏱️ processar_dados executou em 1.0012s
Cache/Memoization
from functools import wraps
def cache(funcao):
"""Cache simples para resultados de função"""
memoria = {}
@wraps(funcao)
def wrapper(*args):
if args in memoria:
print(f"📦 Cache hit para {args}")
return memoria[args]
resultado = funcao(*args)
memoria[args] = resultado
print(f"💾 Armazenando em cache: {args}")
return resultado
return wrapper
@cache
def fibonacci(n):
"""Calcula Fibonacci (recursivo)"""
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # Muito mais rápido com cache!
# Python 3.9+ tem lru_cache built-in:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci_otimizado(n):
if n < 2:
return n
return fibonacci_otimizado(n-1) + fibonacci_otimizado(n-2)
Retry - Tentar Novamente em Caso de Erro
import time
from functools import wraps
def retry(tentativas=3, delay=1, excecoes=(Exception,)):
"""Tenta executar a função múltiplas vezes"""
def decorator(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
ultima_excecao = None
for tentativa in range(1, tentativas + 1):
try:
return funcao(*args, **kwargs)
except excecoes as e:
ultima_excecao = e
print(f"⚠️ Tentativa {tentativa}/{tentativas} falhou: {e}")
if tentativa < tentativas:
print(f" Aguardando {delay}s...")
time.sleep(delay)
raise ultima_excecao
return wrapper
return decorator
@retry(tentativas=3, delay=1)
def buscar_api():
import random
if random.random() < 0.7: # 70% de chance de falhar
raise ConnectionError("Falha na conexão")
return {"status": "success"}
try:
resultado = buscar_api()
print(f"✅ Sucesso: {resultado}")
except ConnectionError as e:
print(f"❌ Falha após todas as tentativas: {e}")
Validador de Tipos
from functools import wraps
def validar_tipos(*tipos_args, **tipos_kwargs):
"""Valida tipos dos argumentos"""
def decorator(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
# Validar args posicionais
for arg, tipo in zip(args, tipos_args):
if not isinstance(arg, tipo):
raise TypeError(
f"Esperado {tipo.__name__}, recebido {type(arg).__name__}"
)
# Validar kwargs
for nome, valor in kwargs.items():
if nome in tipos_kwargs:
tipo = tipos_kwargs[nome]
if not isinstance(valor, tipo):
raise TypeError(
f"{nome}: esperado {tipo.__name__}, "
f"recebido {type(valor).__name__}"
)
return funcao(*args, **kwargs)
return wrapper
return decorator
@validar_tipos(str, int, idade=int)
def criar_usuario(nome, idade):
return {"nome": nome, "idade": idade}
print(criar_usuario("Ana", 25)) # OK
# criar_usuario(123, "25") # TypeError!
Autenticação/Autorização
from functools import wraps
# Simular usuário logado
usuario_atual = {"nome": "Admin", "role": "admin"}
def requer_login(funcao):
"""Verifica se usuário está logado"""
@wraps(funcao)
def wrapper(*args, **kwargs):
if not usuario_atual:
raise PermissionError("❌ Faça login primeiro!")
return funcao(*args, **kwargs)
return wrapper
def requer_role(*roles_permitidas):
"""Verifica se usuário tem permissão"""
def decorator(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
if usuario_atual.get("role") not in roles_permitidas:
raise PermissionError(
f"❌ Acesso negado. Requer: {roles_permitidas}"
)
return funcao(*args, **kwargs)
return wrapper
return decorator
@requer_login
@requer_role("admin", "moderador")
def deletar_usuario(user_id):
return f"✅ Usuário {user_id} deletado"
print(deletar_usuario(42))
🔄 O Que São Generators?
Generators são funções que produzem uma sequência de valores sob demanda, usando yield em vez de return. Eles são extremamente eficientes em memória porque geram valores um de cada vez.
# Função normal - armazena tudo na memória
def lista_quadrados(n):
resultado = []
for i in range(n):
resultado.append(i ** 2)
return resultado
# Generator - gera sob demanda
def generator_quadrados(n):
for i in range(n):
yield i ** 2
# Comparação de memória
import sys
lista = lista_quadrados(1000000)
gen = generator_quadrados(1000000)
print(f"Lista: {sys.getsizeof(lista):,} bytes") # ~8MB
print(f"Generator: {sys.getsizeof(gen)} bytes") # ~120 bytes!
📝 Criando Generators
Generator Simples
def contagem_regressiva(n):
"""Generator de contagem regressiva"""
print("🚀 Iniciando contagem...")
while n > 0:
yield n
n -= 1
print("🔥 Decolagem!")
# Usar com for
for numero in contagem_regressiva(5):
print(numero)
# Ou manualmente com next()
gen = contagem_regressiva(3)
print(next(gen)) # 3
print(next(gen)) # 2
print(next(gen)) # 1
# print(next(gen)) # StopIteration
Generator Infinito
def numeros_pares():
"""Generator infinito de números pares"""
n = 0
while True:
yield n
n += 2
# Usar com cuidado!
pares = numeros_pares()
for _ in range(10):
print(next(pares), end=" ")
# 0 2 4 6 8 10 12 14 16 18
Generator com Múltiplos Yields
def ler_arquivo_grande(caminho, chunk_size=1024):
"""Lê arquivo grande em chunks"""
with open(caminho, 'r') as arquivo:
while True:
chunk = arquivo.read(chunk_size)
if not chunk:
break
yield chunk
# Processar arquivo de 1GB sem carregar na memória
# for chunk in ler_arquivo_grande("arquivo_grande.txt"):
# processar(chunk)
🎯 Generator Expressions
Similar a list comprehension, mas com parênteses:
# List comprehension - cria lista completa
lista = [x**2 for x in range(1000000)]
# Generator expression - gera sob demanda
gen = (x**2 for x in range(1000000))
# Comparação
import sys
print(f"Lista: {sys.getsizeof(lista):,} bytes")
print(f"Generator: {sys.getsizeof(gen)} bytes")
# Usar diretamente em funções
soma = sum(x**2 for x in range(1000)) # Sem [] extras
media = sum(x for x in range(100)) / 100
⚡ yield from - Delegação
def generator_aninhado():
yield from range(3) # 0, 1, 2
yield from "abc" # a, b, c
yield from [10, 20, 30] # 10, 20, 30
for item in generator_aninhado():
print(item, end=" ")
# 0 1 2 a b c 10 20 30
# Exemplo prático: achatar listas
def achatar(lista):
"""Achata listas aninhadas"""
for item in lista:
if isinstance(item, list):
yield from achatar(item)
else:
yield item
aninhada = [1, [2, 3, [4, 5]], 6, [7, 8]]
print(list(achatar(aninhada)))
# [1, 2, 3, 4, 5, 6, 7, 8]
🔧 Métodos de Generator
def generator_interativo():
"""Generator que aceita valores"""
total = 0
while True:
valor = yield total
if valor is None:
break
total += valor
gen = generator_interativo()
print(next(gen)) # Iniciar: 0
print(gen.send(10)) # Enviar 10: 10
print(gen.send(20)) # Enviar 20: 30
print(gen.send(5)) # Enviar 5: 35
# gen.send(None) # Finalizar
🎯 Projeto Prático: Sistema de Pipeline de Dados
Vamos criar um sistema de processamento de dados usando decorators e generators, aplicando conceitos de POO:
import time
import random
from functools import wraps
from typing import Generator, Callable, Any, List
from dataclasses import dataclass
# ============================================
# DECORATORS DO SISTEMA
# ============================================
def timer(funcao):
"""Mede tempo de execução"""
@wraps(funcao)
def wrapper(*args, **kwargs):
inicio = time.perf_counter()
resultado = funcao(*args, **kwargs)
duracao = time.perf_counter() - inicio
print(f"⏱️ {funcao.__name__}: {duracao:.4f}s")
return resultado
return wrapper
def log_pipeline(funcao):
"""Loga etapas do pipeline"""
@wraps(funcao)
def wrapper(*args, **kwargs):
print(f"📍 Iniciando: {funcao.__name__}")
resultado = funcao(*args, **kwargs)
print(f"✅ Concluído: {funcao.__name__}")
return resultado
return wrapper
def retry_generator(tentativas=3, delay=0.5):
"""Retry para generators"""
def decorator(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
for tentativa in range(tentativas):
try:
yield from funcao(*args, **kwargs)
return
except Exception as e:
print(f"⚠️ Erro na tentativa {tentativa+1}: {e}")
if tentativa < tentativas - 1:
time.sleep(delay)
raise RuntimeError(f"Falhou após {tentativas} tentativas")
return wrapper
return decorator
# ============================================
# GENERATORS DO PIPELINE
# ============================================
@dataclass
class DadosBrutos:
id: int
valor: float
timestamp: float
def gerar_dados(quantidade: int) -> Generator[DadosBrutos, None, None]:
"""Generator que simula fonte de dados"""
for i in range(quantidade):
yield DadosBrutos(
id=i,
valor=random.uniform(0, 100),
timestamp=time.time()
)
time.sleep(0.01) # Simular latência
def filtrar_dados(dados: Generator, minimo: float = 20) -> Generator:
"""Filtra dados abaixo do mínimo"""
for dado in dados:
if dado.valor >= minimo:
yield dado
def transformar_dados(dados: Generator) -> Generator[dict, None, None]:
"""Transforma dados em dicionário processado"""
for dado in dados:
yield {
"id": dado.id,
"valor_original": dado.valor,
"valor_processado": round(dado.valor * 1.1, 2),
"categoria": "alto" if dado.valor > 50 else "baixo",
"processado_em": time.time()
}
def agrupar_lotes(dados: Generator, tamanho_lote: int = 10) -> Generator[List, None, None]:
"""Agrupa dados em lotes"""
lote = []
for dado in dados:
lote.append(dado)
if len(lote) >= tamanho_lote:
yield lote
lote = []
if lote: # Último lote parcial
yield lote
# ============================================
# PIPELINE PRINCIPAL
# ============================================
class DataPipeline:
"""Pipeline de processamento de dados"""
def __init__(self, nome: str):
self.nome = nome
self.etapas: List[Callable] = []
self.estatisticas = {
"total_processado": 0,
"total_filtrado": 0,
"lotes_gerados": 0
}
def adicionar_etapa(self, etapa: Callable) -> 'DataPipeline':
"""Adiciona etapa ao pipeline (fluent interface)"""
self.etapas.append(etapa)
return self
@timer
@log_pipeline
def executar(self, dados_entrada: Generator) -> Generator:
"""Executa o pipeline completo"""
dados = dados_entrada
for etapa in self.etapas:
dados = etapa(dados)
return dados
def processar_e_salvar(self, dados: Generator) -> List[dict]:
"""Processa e coleta todos os resultados"""
resultados = []
for lote in dados:
self.estatisticas["lotes_gerados"] += 1
for item in lote:
resultados.append(item)
self.estatisticas["total_processado"] += 1
return resultados
def relatorio(self):
"""Mostra relatório do pipeline"""
print(f"\n📊 RELATÓRIO: {self.nome}")
print("=" * 40)
for chave, valor in self.estatisticas.items():
print(f" {chave}: {valor}")
# ============================================
# DECORATORS DE CLASSE
# ============================================
def singleton(cls):
"""Garante apenas uma instância da classe"""
instancias = {}
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instancias:
instancias[cls] = cls(*args, **kwargs)
return instancias[cls]
return get_instance
@singleton
class ConfigManager:
"""Gerenciador de configurações (singleton)"""
def __init__(self):
self.config = {
"pipeline_ativo": True,
"lote_tamanho": 5,
"valor_minimo": 25
}
def get(self, chave):
return self.config.get(chave)
# ============================================
# EXECUTAR DEMONSTRAÇÃO
# ============================================
if __name__ == "__main__":
print("=" * 60)
print("🚀 SISTEMA DE PIPELINE DE DADOS")
print("=" * 60)
# Obter configurações (singleton)
config = ConfigManager()
print(f"\n⚙️ Configurações: {config.config}")
# Criar pipeline
pipeline = DataPipeline("Pipeline de Vendas")
# Configurar etapas
pipeline.adicionar_etapa(
lambda d: filtrar_dados(d, config.get("valor_minimo"))
).adicionar_etapa(
transformar_dados
).adicionar_etapa(
lambda d: agrupar_lotes(d, config.get("lote_tamanho"))
)
# Gerar dados de entrada
dados_entrada = gerar_dados(50)
# Executar pipeline
print("\n" + "-" * 60)
dados_processados = pipeline.executar(dados_entrada)
# Coletar resultados
resultados = pipeline.processar_e_salvar(dados_processados)
# Relatório
pipeline.relatorio()
# Mostrar alguns resultados
print("\n📄 Primeiros 3 resultados:")
for item in resultados[:3]:
print(f" {item}")
print(f"\n✅ Total de itens processados: {len(resultados)}")
💡 Quando Usar Cada Um
Use Decorators Para:
- ✅ Logging e monitoramento
- ✅ Cache/memoization
- ✅ Autenticação e autorização
- ✅ Validação de entrada
- ✅ Retry em caso de falha
- ✅ Medir tempo de execução
- ✅ Adicionar funcionalidade sem modificar código
Use Generators Para:
- ✅ Processar arquivos grandes
- ✅ Streaming de dados
- ✅ Pipelines de dados
- ✅ Sequências infinitas
- ✅ Economizar memória
- ✅ Lazy evaluation
📚 Decorators Built-in do Python
class MinhaClasse:
@property
def valor(self):
"""Getter como propriedade"""
return self._valor
@valor.setter
def valor(self, novo_valor):
"""Setter"""
self._valor = novo_valor
@staticmethod
def metodo_estatico():
"""Não recebe self"""
return "Estático"
@classmethod
def metodo_classe(cls):
"""Recebe a classe como primeiro argumento"""
return cls.__name__
# Do functools
from functools import lru_cache, cached_property, singledispatch
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
🚀 Próximos Passos
- Funções - Base para decorators
- POO - Decorators de classe
- List Comprehension - Comparar com generators
- Try/Except - Decorators de retry
- Arquivos - Generators para arquivos grandes
- Async/Await - Programação assíncrona
- Context Managers - with statement customizado
Quer dominar Python avançado? Confira nosso curso completo com projetos profissionais!
📝 Resumo
- ✅ Decorators - modificam comportamento de funções
- ✅ @wraps para preservar metadados
- ✅ Decorators práticos: timer, cache, retry, auth
- ✅ Decorators com parâmetros
- ✅ Generators - produzem valores sob demanda
- ✅ yield vs return
- ✅ Generator expressions
- ✅ yield from para delegação
- ✅ Projeto: Pipeline de dados completo
- ✅ Singleton decorator
- ✅ Decorators built-in do Python
Decorators e Generators são ferramentas poderosas que elevam seu código Python a outro nível. Domine-os e você estará pronto para criar aplicações profissionais e eficientes!