Desvendando os Geradores em Python: O Poder Mágico do yield

Geradores são uma funcionalidade elegante do Python que permitem criar sequências de dados de forma preguiçosa, economizando memória e processamento. O segredo está na palavra-chave yield, que transforma uma função comum em uma fábrica de valores sob demanda.

O que são geradores?

Um gerador é um objeto que pode ser iterado, mas ao contrário de listas ou tuplas, ele não armazena todos os valores na memória. Em vez disso, gera cada elemento apenas quando solicitado, através de iterações (for ou next()).

Existem duas formas principais de criar geradores:

  • Função geradora: definida com def e contendo ao menos uma declaração yield.
  • Expressão geradora: sintaxe similar à compreensão de listas, mas usando parênteses: (expressão for item in iterável).

Exemplo prático: processamento de arquivos grandes

Suponha que você precise contar as ocorrências da palavra "falha" em um arquivo de log de 15 GB. Com geradores, o consumo de memória permanece mínimo, pois apenas uma linha é carregada por vez.

def ler_log_linha_por_linha(caminho):
    """Gerador que retorna cada linha de um arquivo."""
    with open(caminho, 'r', encoding='utf-8') as arquivo:
        for linha in arquivo:  # arquivo já é um iterável
            yield linha

contador_falhas = 0
gerador_log = ler_log_linha_por_linha("servidor.log")

for linha in gerador_log:
    if "falha" in linha.lower():
        contador_falhas += 1

print(f"Total de falhas encontradas: {contador_falhas}")

Nesse código, não importa o tamanho do arquivo; a memória ocupada é sempre proporcional a uma única linha.

Como a mágica do yield funciona?

Quando uma função contém yield, ela não é executada imediatamente ao ser chamada. Em vez disso, retorna um objeto gerador. A execução só começa quando você itera sobre ele, e cada chamada a next() executa o código até o próximo yield, pausando a função e retornando o valor gerado.

O exemplo a seguir ilustra esse comportamento passo a passo:

def contador():
    print("Iniciando contagem...")
    for i in range(1, 4):
        print(f"Prestes a gerar: {i}")
        yield i
        print("Continuando após o yield")
    print("Contagem finalizada")

gen = contador()
print("Função ainda não executou:", gen)  # <generator object contador at 0x...>

# Primeira iteração
print(next(gen))  # Executa até o primeiro yield → imprime "Iniciando..." e "Prestes a gerar: 1" → retorna 1

# Segunda iteração
print(next(gen))  # Continua depois do yield → imprime "Continuando após yield" e "Prestes a gerar: 2" → retorna 2

# Terceira iteração usando for para capturar o restante
for valor in gen:
    print("Recebido do for:", valor)

A saída será:

Iniciando contagem...
Prestes a gerar: 1
1
Continuando após o yield
Prestes a gerar: 2
2
Continuando após o yield
Prestes a gerar: 3
3
Continuando após o yield
Contagem finalizada

Cada chamada a next() ou iteração no for avança a execução até o próximo yield, mantendo o estado da função entre as pausas. Quando não há mais yield, o gerador levanta a exceção StopIteration, encerrando o loop.

Principais vantagens dos geradores

  • Memória eficiente: processam grandes volumes de dados sem sobrecarregar a RAM.
  • Lazy evaluation: só calculam o que é necessário, no momento da iteração.
  • Composição: podem ser encadeados para formar pipelines de processamento.

Tags: Python geradores yield iteradores arquivos grandes

Publicado em 7-21 13:49