Python: Iteradores e Geradores
Última atualização: 2026-09-22
Você tem usado
for x in lista:para iterar dados, mas já pensou no que pode seguirin? Por querange(1000000)não ocupa memória? Iteradores e geradores são a resposta. Eles permitem lidar com fluxos de dados "infinitos" — gerados sob demanda, não armazenados na memória.
1. O que é o Protocolo de Iterador
Em Python, qualquer objeto que pode ser usado em um laço for é iterável. Eles implementam o protocolo de iterador:
▶ Exemplo: Básico do Protocolo de Iterador
▶ ExperimenteSaída:
# Executed successfully
Um laço for essencialmente faz isto:
▶ Exemplo: Simulando um Laço for Manualmente
▶ ExperimenteSaída:
# Executed successfully
2. Iteradores Personalizados
Implemente __iter__ e __next__ para fazer sua classe funcionar com laços for:
▶ Exemplo: Iterador de Contagem Regressiva
▶ ExperimenteSaída:
# Function defined successfully
▶ Exemplo: Iterador de Fibonacci (Dificuldade ⭐⭐)
▶ ExperimenteSaída:
# Function defined successfully
3. Geradores: yield
Escrever uma classe com __iter__ e __next__ é tedioso. Funções geradoras usam yield para alcançar o mesmo resultado:
▶ Exemplo: Gerador de Contagem Regressiva
▶ ExperimenteSaída:
# Function defined successfully
yield é diferente de return — return encerra uma função, enquanto yield pausa ela, lembra o estado atual e retoma de onde parou na próxima chamada.
▶ Exemplo: Gerador de Fibonacci
▶ ExperimenteSaída:
# Function defined successfully
4. Carregamento Preguiçoso com Geradores
A maior vantagem dos geradores — eles não geram todos os dados de uma vez, mas sob demanda:
▶ Exemplo: Carregamento Preguiçoso de Gerador
▶ ExperimenteSaída:
Gerando 1
Gerando 2
Gerando 3
Gerador criado, mas ainda não executado
Vê o fluxo de execução? Cada chamada a next() executa até o próximo yield, depois pausa. Isso é incrivelmente útil para grandes dados:
▶ Exemplo: Lendo Grandes Arquivos Linha por Linha
# Forma "preguiçosa" de processar grandes arquivos
def read_large_file(filename):
"""Lê um grande arquivo linha por linha, sem carregar tudo na memória"""
with open(filename, "r", encoding="utf-8") as f:
for line in f:
yield line.strip()
# Processa com gerador — apenas uma linha na memória por vez
for line in read_large_file("huge_log.txt"):
if "ERRO" in line:
print(line) # Velocidade limitada apenas pela velocidade de leitura do disco
5. Expressões Geradoras
Similar a list comprehensions, mas com parênteses — expressões geradoras são avaliadas preguiçosamente:
▶ Exemplo: Expressão Geradora
▶ ExperimenteSaída:
# Executed successfully
| Comparação | List Comprehension [] |
Expressão Geradora () |
|---|---|---|
| Memória | Todos os dados na memória | Sob demanda, quase nenhuma |
| Velocidade | Geração e acesso rápidos | Preguiçosa, computa cada vez |
| Reutilizável | Pode iterar várias vezes | Só pode iterar uma vez |
| Caso de uso | Dados pequenos, uso repetido | Grandes dados, passagem única |
6. Casos de Uso Comuns
- Processamento de grandes dados: Leia grandes arquivos de log/CSV linha por linha, evitando estouro de memória.
- Sequências infinitas: Geradores podem representar fluxos de dados "infinitos" — Fibonacci, dados de sensores.
- Processamento em pipeline: Encadeie múltiplos geradores em um pipeline de processamento de dados.
- Iteração reversa:
reversed()retorna um iterador para alguns tipos. - Avaliação preguiçosa: Funções retornam um gerador; o chamador decide quantos valores consumir.
❓ Perguntas Frequentes
return para retornar um resultado uma vez e termina. Um gerador usa yield para produzir valores um de cada vez, pausando e salvando o estado após cada yield. Cada chamada a uma função geradora retorna um novo objeto gerador.list(gen) para converter o gerador em uma lista. Mas se os dados forem muito grandes, converter para lista esgotará a memória. Um compromisso: use itertools.tee() para copiar o gerador, ou chame a função geradora novamente para criar um novo.yield e return podem coexistir?return em um gerador efetivamente para a iteração e levanta StopIteration com um valor opcional. Mas é raramente usado. Se você encontrar uma função que precisa tanto de yield quanto de return, repense o design.📖 Resumo
- Objetos iteráveis suportam laços
for; nos bastidores, implementam o protocolo__iter__e__next__ - Iteração manual:
iter()obtém um iterador,next()obtém o próximo valor,StopIterationencerra - Funções geradoras usam
yieldpara produzir valores um de cada vez, salvando estado automaticamente - Geradores são preguiçosos — produzem o necessário, sem desperdício de memória
- Expressões geradoras
(x for x in seq)são como list comprehensions, mas avaliadas preguiçosamente yield fromdelega a outro gerador (avançado, aprenda quando precisar)
📝 Exercícios
-
Básico (Dificuldade ⭐): Escreva um gerador
even_numbers(max_n)que gera todos os números pares de 0 atémax_n. -
Intermediário (Dificuldade ⭐⭐): Escreva um gerador
repeat_list(items, times)que repete cada elemento de uma listatimesvezes. Por exemplo,list(repeat_list([1, 2, 3], 2))retorna[1, 1, 2, 2, 3, 3]. -
Desafio (Dificuldade ⭐⭐⭐): Escreva um gerador
deep_flatten(nested)que achata listas arbitrariamente aninhadas. Por exemplo,list(deep_flatten([1, [2, [3, 4]], 5]))retorna[1, 2, 3, 4, 5]. Dica: Itere sobre os elementos; se um elemento for uma lista, gere recursivamente a partir dele comyield from.