<h2>Introdução aos Generators com Comunicação Bidirecional</h2>
<p>Generators em Python são funções que utilizam <code>yield</code> para produzir uma sequência de valores sob demanda. O que muitos desenvolvedores não sabem é que generators podem fazer muito mais: eles permitem <strong>comunicação bidirecional</strong> através dos métodos <code>send()</code>, <code>throw()</code> e <code>close()</code>. Essa capacidade transforma generators em máquinas de estado poderosas e elegantes, ideais para implementar corrotinas, processamento de fluxos de dados e controle fino de execução. Neste artigo, exploraremos como dominar essa funcionalidade avançada.</p>
<h2>Comunicação Bidirecional: <code>send()</code>, <code>throw()</code> e <code>close()</code></h2>
<h3>O método <code>send()</code></h3>
<p>O <code>send()</code> permite enviar valores <strong>para dentro</strong> de um generator enquanto ele está pausado em um <code>yield</code>. Quando um generator recebe um valor via <code>send()</code>, a expressão <code>yield</code> retorna esse valor, permitindo que a função processe e tome decisões baseadas nele.</p>
<pre><code class="language-python">def gerador_interativo():
print("Iniciando generator")
x = yield "Pronto para receber dados"
print(f"Recebi: {x}")
y = yield f"Valor processado: {x * 2}"
print(f"Novo valor: {y}")
yield "Finalizando"
Uso
gen = gerador_interativo()
print(next(gen)) # Inicia o generator
print(gen.send(10)) # Envia 10, x recebe 10
print(gen.send(5)) # Envia 5, y recebe 5</code></pre>
<p><strong>Saída:</strong></p>
<pre><code>Iniciando generator
Pronto para receber dados
Recebi: 10
Valor processado: 20
Novo valor: 5
Finalizando</code></pre>
<h3>Os métodos <code>throw()</code> e <code>close()</code></h3>
<p><code>throw()</code> injeta uma exceção dentro do generator, permitindo tratamento de erros sofisticado. <code>close()</code> encerra o generator injetando uma <code>GeneratorExit</code>.</p>
<pre><code class="language-python">def processador_resiliente():
try:
while True:
valor = yield
if valor is None:
print("Pulando valores nulos")
continue
print(f"Processando: {valor}")
except ValueError as e:
print(f"Erro capturado: {e}")
except GeneratorExit:
print("Generator encerrado corretamente")
gen = processador_resiliente()
next(gen)
gen.send(5)
gen.send(None)
gen.send(10)
try:
gen.throw(ValueError, ValueError("Dado inválido"))
except ValueError:
pass
gen.close()</code></pre>
<h2>Padrões Avançados: Corrotinas e Pipelines de Dados</h2>
<h3>Corrotinas para Processamento Assíncrono</h3>
<p>Uma corrotina é um generator decorado com <code>@coroutine</code> (ou usando <code>async/await</code> moderno) que implementa um padrão produtor-consumidor. Elas são especialmente úteis para processar streams de dados com estado.</p>
<pre><code class="language-python">from functools import wraps
def coroutine(func):
"""Decorator que inicia o generator automaticamente"""
@wraps(func)
def wrapper(args, *kwargs):
gen = func(args, *kwargs)
next(gen) # Executa até o primeiro yield
return gen
return wrapper
@coroutine
def contador_acumulador():
total = 0
while True:
valor = yield total
if valor is not None:
total += valor
@coroutine
def filtro_pares():
while True:
valor = yield
if valor % 2 == 0:
print(f"Par encontrado: {valor}")
Uso em pipeline
acc = contador_acumulador()
print(acc.send(5)) # 5
print(acc.send(3)) # 8
print(acc.send(2)) # 10</code></pre>
<h3>Pipeline de Transformação</h3>
<p>Pipelines combinam múltiplos generators para processar dados em etapas. Cada estágio recebe dados, processa e passa adiante.</p>
<pre><code class="language-python">@coroutine
def produtor(dados):
for item in dados:
print(f"[Produtor] Enviando: {item}")
yield item
@coroutine
def transformador(proximo):
while True:
valor = yield
resultado = valor * 2
print(f"[Transformador] {valor} → {resultado}")
proximo.send(resultado)
@coroutine
def consumidor():
while True:
valor = yield
print(f"[Consumidor] Recebeu: {valor}\n")
Montando pipeline
dados = [1, 2, 3, 4, 5]
cons = consumidor()
trans = transformador(cons)
for item in dados:
trans.send(item)
trans.close()</code></pre>
<h2>Controle de Fluxo e Máquinas de Estado</h2>
<h3>Implementando Máquinas de Estado</h3>
<p>Generators permitem implementar máquinas de estado de forma elegante, mantendo o estado interno sem necessidade de classes complexas.</p>
<pre><code class="language-python">def maquina_estados():
estado = "inicio"
while True:
evento = yield estado
if estado == "inicio":
if evento == "conectar":
estado = "conectado"
elif evento == "erro":
estado = "erro"
elif estado == "conectado":
if evento == "enviar":
estado = "enviando"
elif evento == "desconectar":
estado = "desconectado"
elif estado == "enviando":
if evento == "sucesso":
estado = "conectado"
elif evento == "falha":
estado = "erro"
elif estado == "erro":
if evento == "reconectar":
estado = "inicio"
Simulação
maq = maquina_estados()
print(next(maq)) # inicio
print(maq.send("conectar")) # conectado
print(maq.send("enviar")) # enviando
print(maq.send("sucesso")) # conectado
print(maq.send("desconectar")) # desconectado</code></pre>
<h3>Delegação com <code>yield from</code></h3>
<p><code>yield from</code> simplifica a delegação para sub-generators, permitindo composição elegante:</p>
<pre><code class="language-python">def gerador_a():
yield 1
yield 2
def gerador_b():
yield 3
yield 4
def gerador_delegado():
yield from gerador_a()
yield from gerador_b()
yield 5
for valor in gerador_delegado():
print(valor) # 1, 2, 3, 4, 5</code></pre>
<h2>Conclusão</h2>
<p>Os três aprendizados principais que consolidam seu domínio sobre generators avançados são: <strong>(1)</strong> Comunicação bidirecional via <code>send()</code>, <code>throw()</code> e <code>close()</code> transforma generators em estruturas ativas capazes de processar dados contextualizados; <strong>(2)</strong> Padrões como corrotinas e pipelines habilitam processamento de streams elegante e modular, reduzindo complexidade em código de produção; <strong>(3)</strong> Máquinas de estado implementadas com generators oferecem alternativa limpa e performática à orientação a objetos pesada, ideal para sistemas com múltiplos estados e transições.</p>
<p>Pratique construindo pequenos projetos: um parser de eventos, um simulador de fila de processamento ou um sistema de logging com backpressure. A prática solidifica a intuição sobre quando e como usar cada recurso.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.python.org/3/howto/functional.html#generators" target="_blank" rel="noopener noreferrer">Python Generator Expressions - Documentação Oficial</a></li>
<li><a href="https://www.python.org/dev/peps/pep-0342/" target="_blank" rel="noopener noreferrer">PEP 342 - Coroutines via Enhanced Generators</a></li>
<li><a href="https://realpython.com/generators-iterators-python/" target="_blank" rel="noopener noreferrer">Real Python - Generators and Yield</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ítulos 14-17)</a></li>
<li><a href="https://docs.python.org/3/library/asyncio.html" target="_blank" rel="noopener noreferrer">Asyncio Documentation - Event Loop and Coroutines</a></li>
</ul>