<h2>Entendendo Generators: O Que Realmente São</h2>
<p>Um generator em Python é uma função que produz uma sequência de valores, mas diferentemente de uma função comum que retorna todos os valores de uma vez, um generator entrega esses valores sob demanda. Internamente, um generator mantém seu estado entre chamadas sucessivas, permitindo que você processe grandes volumes de dados sem carregar tudo na memória simultânea.</p>
<p>A forma mais simples de criar um generator é usar a palavra-chave <code>yield</code> dentro de uma função. Quando Python encontra <code>yield</code>, a função não retorna imediatamente — ela pausa sua execução e retorna o valor especificado. Na próxima chamada, a função resume exatamente de onde parou. Isso é fundamentalmente diferente de <code>return</code>, que encerra a função e descarta seu estado local.</p>
<pre><code class="language-python"># Função comum que retorna uma lista
def numeros_normais():
resultado = []
for i in range(5):
resultado.append(i * 2)
return resultado
Generator que produz valores sob demanda
def numeros_generator():
for i in range(5):
yield i * 2
Usando a função comum
print(numeros_normais()) # [0, 2, 4, 6, 8]
Usando o generator
gen = numeros_generator()
print(next(gen)) # 0
print(next(gen)) # 2
print(next(gen)) # 4</code></pre>
<p>A diferença crucial está no uso de memória. A função comum cria e armazena toda a lista em memória antes de retornar. O generator, por sua vez, calcula cada valor sob demanda e descarta o anterior, ocupando uma fração do espaço de memória.</p>
<h2>Lazy Evaluation: Computação Sob Demanda</h2>
<p>Lazy evaluation (avaliação preguiçosa) é o princípio central que torna generators poderosos. Em vez de processar dados antecipadamente, você computa apenas o que é necessário, quando é necessário. Isso é especialmente valioso ao trabalhar com dados infinitos, fluxos contínuos ou arquivos gigantes.</p>
<p>Considere um cenário real: você precisa processar um arquivo com 10 gigabytes de linhas. Se você usasse uma abordagem tradicional, teria que carregar o arquivo inteiro na memória antes de processar a primeira linha. Com lazy evaluation, você lê e processa linha por linha, mantendo apenas a atual na memória.</p>
<pre><code class="language-python"># Abordagem tradicional (problema de memória)
def ler_arquivo_inteiro(caminho):
linhas = []
with open(caminho, 'r') as f:
for linha in f:
linhas.append(linha.strip())
return linhas
Abordagem com lazy evaluation
def ler_arquivo_lazy(caminho):
with open(caminho, 'r') as f:
for linha in f:
yield linha.strip()
Processando dados
for linha in ler_arquivo_lazy('dados.txt'):
processar(linha) # Apenas uma linha na memória por vez</code></pre>
<h3>Expressões Geradoras</h3>
<p>Python oferece uma sintaxe compacta para criar generators sem usar <code>def</code> e <code>yield</code>. Expressões geradoras funcionam como list comprehensions, mas usam parênteses em vez de colchetes:</p>
<pre><code class="language-python"># List comprehension (cria lista inteira na memória)
quadrados_lista = [x**2 for x in range(1000000)]
Expressão geradora (computa sob demanda)
quadrados_gen = (x**2 for x in range(1000000))
print(type(quadrados_lista)) # <class 'list'>
print(type(quadrados_gen)) # <class 'generator'>
Consumindo o generator
for quad in quadrados_gen:
if quad > 50:
print(quad)
break</code></pre>
<p>Expressões geradoras são ideais quando você precisa processar dados sequencialmente e não necessita acessá-los aleatoriamente (sem usar índices).</p>
<h2>Pipelines de Dados com Generators</h2>
<p>Um pipeline de dados é uma série de transformações aplicadas sequencialmente a um fluxo de dados. Generators são perfeitos para construir pipelines porque cada estágio processa dados sob demanda, criando um fluxo eficiente que nunca mantém os dados completos na memória.</p>
<p>A arquitetura é simples: cada função geradora consome dados de um gerador anterior e produz dados transformados para o próximo. Essa abordagem separa responsabilidades, facilita testes e otimiza o consumo de memória.</p>
<pre><code class="language-python"># Estágio 1: Leitura de dados
def ler_numeros(inicio, fim):
for i in range(inicio, fim):
print(f" Lendo {i}")
yield i
Estágio 2: Filtrar números pares
def filtrar_pares(sequencia):
for numero in sequencia:
if numero % 2 == 0:
print(f" Filtrando {numero}")
yield numero
Estágio 3: Multiplicar por 2
def multiplicar(sequencia, fator):
for numero in sequencia:
resultado = numero * fator
print(f" Multiplicando {numero} por {fator} = {resultado}")
yield resultado
Construir o pipeline
pipeline = multiplicar(
filtrar_pares(
ler_numeros(1, 11)
),
fator=10
)
Consumir o pipeline
print("\nResultados:")
for resultado in pipeline:
print(f"Resultado final: {resultado}")</code></pre>
<p><strong>Saída esperada:</strong></p>
<pre><code>Lendo 1
Lendo 2
Filtrando 2
Multiplicando 2 por 10 = 20
Resultado final: 20
Lendo 3
Lendo 4
Filtrando 4
Multiplicando 4 por 10 = 40
Resultado final: 40
[...]</code></pre>
<h3>Pipelines Práticos com CSV</h3>
<p>Aqui está um exemplo realista de processamento de dados CSV sem carregar tudo na memória:</p>
<pre><code class="language-python">import csv
from datetime import datetime
Estágio 1: Ler CSV linha por linha
def ler_csv(caminho):
with open(caminho, 'r', encoding='utf-8') as f:
leitor = csv.DictReader(f)
for linha in leitor:
yield linha
Estágio 2: Filtrar registros válidos
def filtrar_validos(linhas):
for linha in linhas:
try:
float(linha['valor'])
yield linha
except (ValueError, KeyError):
continue
Estágio 3: Enriquecer com data de processamento
def adicionar_timestamp(linhas):
for linha in linhas:
linha['data_processamento'] = datetime.now().isoformat()
yield linha
Estágio 4: Agrupar por categoria
def agrupar_por_categoria(linhas):
grupos = {}
for linha in linhas:
categoria = linha.get('categoria', 'sem_categoria')
if categoria not in grupos:
grupos[categoria] = []
grupos[categoria].append(linha)
for categoria, items in grupos.items():
yield {'categoria': categoria, 'itens': items}
Executar pipeline
pipeline = agrupar_por_categoria(
adicionar_timestamp(
filtrar_validos(
ler_csv('vendas.csv')
)
)
)
Processar resultado
for grupo in pipeline:
print(f"Categoria: {grupo['categoria']}, Quantidade: {len(grupo['itens'])}")</code></pre>
<h3>Composição com itertools</h3>
<p>A biblioteca padrão <code>itertools</code> oferece generators prontos que facilitam composição de pipelines:</p>
<pre><code class="language-python">import itertools
Dados de exemplo
dados = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Pipeline: pegar 5 primeiros, filtrar pares, multiplicar por 2
resultado = map(
lambda x: x * 2,
filter(
lambda x: x % 2 == 0,
itertools.islice(dados, 5)
)
)
print(list(resultado)) # [4, 8]
Usando itertools.chain para combinar múltiplas sequências
seq1 = [1, 2, 3]
seq2 = [4, 5, 6]
combinado = itertools.chain(seq1, seq2)
for valor in combinado:
print(valor) # 1, 2, 3, 4, 5, 6
itertools.cycle para repetir sequências infinitamente
ciclo = itertools.cycle(['A', 'B', 'C'])
print([next(ciclo) for _ in range(5)]) # ['A', 'B', 'C', 'A', 'B']</code></pre>
<h2>Erros Comuns e Armadilhas</h2>
<h3>Consumindo Generators Múltiplas Vezes</h3>
<p>Um erro frequente é tentar iterar sobre o mesmo generator duas vezes. Após ser consumido, um generator é esgotado e não pode ser reutilizado.</p>
<pre><code class="language-python">gen = (x**2 for x in range(5))
Primeira iteração funciona
print(list(gen)) # [0, 1, 4, 9, 16]
Segunda iteração retorna vazio
print(list(gen)) # []</code></pre>
<p>Se você precisa reutilizar os dados, converta para lista (se a memória permitir) ou crie um novo generator:</p>
<pre><code class="language-python"># Solução 1: Converter para lista (cuidado com memória)
dados = list(gen)
print(list(dados))
print(list(dados))
Solução 2: Criar função que retorna novo generator
def criar_gen():
return (x**2 for x in range(5))
print(list(criar_gen()))
print(list(criar_gen()))</code></pre>
<h3>StopIteration e next()</h3>
<p>Ao usar <code>next()</code> diretamente, você recebe <code>StopIteration</code> quando o generator se esgota. Use a forma segura com valor padrão:</p>
<pre><code class="language-python">gen = (x for x in range(3))
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen, None)) # None (ao invés de StopIteration)</code></pre>
<h3>Debugging de Generators</h3>
<p>Generators podem ser difíceis de debugar porque não computam valores até serem consumidos. Uma técnica útil é usar <code>itertools.tee()</code> para criar cópias independentes:</p>
<pre><code class="language-python">import itertools
gen = (x**2 for x in range(5))
Criar duas cópias independentes
gen1, gen2 = itertools.tee(gen, 2)
Debugar uma cópia sem afetar a outra
print("Debug:", list(gen1))
print("Consumir:", list(gen2))</code></pre>
<h2>Conclusão</h2>
<p>Os três pontos fundamentais que você aprendeu neste artigo são:</p>
<ol>
<li><strong>Generators são máquinas de estado</strong> que mantêm contexto entre chamadas, viabilizando a implementação de lazy evaluation sem necessidade de armazenar dados completos em memória. Isso transforma a forma como você trabalha com dados grandes ou contínuos.</li>
</ol>
<ol>
<li><strong>Pipelines de dados com generators</strong> oferecem composição elegante e eficiente de transformações, onde cada estágio processa sob demanda. Essa abordagem separa responsabilidades, facilita testes unitários e reduz drasticamente o consumo de memória em comparação com abordagens que materializam dados intermediários.</li>
</ol>
<ol>
<li><strong>Expressões geradoras e a biblioteca itertools</strong> fornecem ferramentas prontas que eliminam a necessidade de escrever generators manualmente em muitos casos, tornando o código mais conciso e legível mantendo todos os benefícios de lazy evaluation.</li>
</ol>
<p>Domine esses conceitos e você terá uma ferramenta poderosa para lidar com processamento de dados em escala, desde leitura de arquivos gigantes até construção de pipelines ETL complexos.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.python.org/3/howto/functional.html#generators" target="_blank" rel="noopener noreferrer">Python Official Documentation - Generators</a></li>
<li><a href="https://realpython.com/generators-and-yield-in-python/" target="_blank" rel="noopener noreferrer">Real Python - Generators and yield</a></li>
<li><a href="https://www.dabeaz.com/generators/" target="_blank" rel="noopener noreferrer">David Beazley - Generator Tricks for Systems Programmers</a></li>
<li><a href="https://www.oreilly.com/library/view/fluent-python-2nd/9781492126249/" target="_blank" rel="noopener noreferrer">Fluent Python - Luciano Ramalho (Capítulo sobre Iterables e Generators)</a></li>
<li><a href="https://docs.python.org/3/library/itertools.html" target="_blank" rel="noopener noreferrer">Python itertools Documentation</a></li>
</ul>
<p><!-- FIM --></p>