Geradores e Iteradores em Python: guia com exemplos
Aprenda a processar milhões de dados sem estourar a memória. Geradores são o segredo dos programas eficientes.
O problema: tudo na memória de uma vez
Imagine que você precisa processar um arquivo com 10 milhões de linhas. O instinto natural é:
# Carrega TUDO na memoria — pode travar o computador
linhas = open("arquivo_gigante.txt").readlines()
for linha in linhas:
processa(linha)
Com geradores, você processa uma linha por vez, sem nunca carregar tudo na memória:
# Processa uma linha por vez — memoria constante
for linha in open("arquivo_gigante.txt"):
processa(linha)
Essa é a magia dos iteradores e geradores.
Iteráveis vs Iteradores
Antes de falar de geradores, vamos entender a diferença:
- Iterável: qualquer coisa que você pode percorrer com
for(listas, strings, dicts, arquivos) - Iterador: o objeto que controla a iteração, lembrando em qual posição está
lista = [1, 2, 3] # iteravel
iterador = iter(lista) # iterador
print(next(iterador)) # 1
print(next(iterador)) # 2
print(next(iterador)) # 3
print(next(iterador)) # StopIteration!
O for faz isso automaticamente: chama iter() no iterável e depois next() até receber StopIteration.
Geradores: iteradores sem esforço
Um gerador é uma função que usa yield em vez de return:
def contagem_regressiva(n):
while n > 0:
yield n
n -= 1
for numero in contagem_regressiva(5):
print(numero)
# 5, 4, 3, 2, 1
A diferença crucial: a função pausa a cada yield e continua de onde parou quando o próximo valor é pedido. Ela não roda tudo de uma vez.
yield vs return
# Com return — cria a lista inteira na memoria
def quadrados_lista(n):
resultado = []
for i in range(n):
resultado.append(i ** 2)
return resultado
# Com yield — gera um valor por vez
def quadrados_gerador(n):
for i in range(n):
yield i ** 2
# Lista: ocupa memoria proporcional a n
lista = quadrados_lista(1_000_000) # ~8 MB na memoria
# Gerador: ocupa memoria constante
gerador = quadrados_gerador(1_000_000) # quase nada na memoria
Generator expressions
Assim como list comprehensions criam listas, generator expressions criam geradores:
# List comprehension — cria lista na memoria
quadrados = [x ** 2 for x in range(1_000_000)]
# Generator expression — gera sob demanda
quadrados = (x ** 2 for x in range(1_000_000))
A única diferença visual: [] vs (). A diferença prática: memória.
import sys
lista = [x ** 2 for x in range(1_000_000)]
gerador = (x ** 2 for x in range(1_000_000))
print(sys.getsizeof(lista)) # ~8 MB
print(sys.getsizeof(gerador)) # ~200 bytes
Quando usar geradores
Processar arquivos grandes
def buscar_erros(caminho):
with open(caminho) as f:
for linha in f:
if "ERROR" in linha:
yield linha.strip()
for erro in buscar_erros("app.log"):
print(erro)
Gerar sequências infinitas
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Pega os 10 primeiros
from itertools import islice
primeiros = list(islice(fibonacci(), 10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Encadear transformações (pipelines)
def ler_linhas(caminho):
with open(caminho) as f:
for linha in f:
yield linha.strip()
def filtrar_vazias(linhas):
for linha in linhas:
if linha:
yield linha
def para_maiusculo(linhas):
for linha in linhas:
yield linha.upper()
# Pipeline — nada e carregado na memoria
linhas = ler_linhas("dados.txt")
linhas = filtrar_vazias(linhas)
linhas = para_maiusculo(linhas)
for linha in linhas:
print(linha)
Cada etapa processa uma linha por vez. É como uma esteira de fábrica.
yield from: delegando para outro gerador
Quando um gerador precisa produzir valores de outro iterável, use yield from:
def numeros():
yield from range(3) # 0, 1, 2
yield from range(10, 13) # 10, 11, 12
list(numeros()) # [0, 1, 2, 10, 11, 12]
Sem yield from, você precisaria de um for explícito:
# Equivalente sem yield from
def numeros():
for n in range(3):
yield n
for n in range(10, 13):
yield n
O módulo itertools
O Python tem um módulo inteiro dedicado a iteradores eficientes:
from itertools import chain, count, cycle, islice, groupby
# chain — junta iteraveis
list(chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5]
# count — contador infinito
list(islice(count(10, 2), 5)) # [10, 12, 14, 16, 18]
# cycle — repete infinitamente
cores = cycle(["vermelho", "verde", "azul"])
list(islice(cores, 7))
# ['vermelho', 'verde', 'azul', 'vermelho', 'verde', 'azul', 'vermelho']
Cuidados com geradores
Geradores se esgotam
gen = (x for x in range(3))
print(list(gen)) # [0, 1, 2]
print(list(gen)) # [] — ja foi consumido!
Se você precisa iterar mais de uma vez, converta para lista ou crie o gerador novamente.
Não dá para acessar por índice
gen = (x for x in range(10))
# gen[5] # TypeError! Geradores nao suportam indexacao
Se você precisa de acesso aleatório, use uma lista.
Resumo
| Conceito | O que faz | Quando usar |
|---|---|---|
iter() / next() |
Protocolo de iteração | Entender como for funciona |
yield |
Cria um gerador | Processar dados sob demanda |
Generator expression () |
Gerador em uma linha | Substituir list comprehension quando memória importa |
yield from |
Delega para outro iterável | Compor geradores |
itertools |
Ferramentas para iteradores | Combinar, filtrar, agrupar iteráveis |
Geradores são um dos recursos mais poderosos do Python. Quando você domina geradores, consegue processar qualquer volume de dados com elegância e eficiência.