Iteradores: __iter__ e __next__
1. Introdução aos Iteradores em Python
Para compreender iteradores em Python, precisamos primeiro distinguir dois conceitos fundamentais: iterável e iterador. Um objeto iterável é aquele que pode ser percorrido em um loop for, como listas, tuplas, strings e dicionários. Já um iterador é um objeto que mantém o estado da iteração e sabe como produzir o próximo valor.
Quando você escreve for item in minha_lista, o Python faz algo interessante por baixo dos panos: ele chama a função iter() no objeto iterável, que retorna um iterador. Em seguida, chama repetidamente next() nesse iterador até que a exceção StopIteration seja levantada.
A diferença crucial é que objetos iteráveis implementam __iter__, enquanto iteradores implementam tanto __iter__ quanto __next__. Todo iterador é iterável, mas nem todo iterável é um iterador.
2. O Protocolo do Iterador: __iter__ e __next__
O protocolo do iterador em Python é elegantemente simples. Vamos examinar cada método:
__iter__(self): Deve retornar o objeto iterador. Em muitos casos, retorna self se a própria classe for o iterador.
__next__(self): Deve retornar o próximo valor disponível. Quando não houver mais valores, deve levantar StopIteration.
Vejamos um exemplo mínimo de um contador manual:
class Contador:
def __init__(self, limite):
self.limite = limite
self.atual = 0
def __iter__(self):
return self
def __next__(self):
if self.atual >= self.limite:
raise StopIteration
valor = self.atual
self.atual += 1
return valor
# Uso
contador = Contador(5)
for numero in contador:
print(numero) # 0, 1, 2, 3, 4
3. Criando uma Classe Iterável Personalizada
Agora, vamos criar uma classe iterável que não é um iterador, mas retorna um objeto iterador separado. Isso é útil quando queremos permitir múltiplas iterações independentes.
class ListaPersonalizada:
def __init__(self, dados):
self.dados = dados
def __iter__(self):
return IteradorLista(self.dados)
class IteradorLista:
def __init__(self, dados):
self.dados = dados
self.indice = 0
def __iter__(self):
return self
def __next__(self):
if self.indice >= len(self.dados):
raise StopIteration
valor = self.dados[self.indice]
self.indice += 1
return valor
# Exemplo prático: MeuRange
class MeuRange:
def __init__(self, inicio, fim, passo=1):
self.inicio = inicio
self.fim = fim
self.passo = passo
def __iter__(self):
return IteradorRange(self.inicio, self.fim, self.passo)
class IteradorRange:
def __init__(self, inicio, fim, passo):
self.atual = inicio
self.fim = fim
self.passo = passo
def __iter__(self):
return self
def __next__(self):
if self.atual >= self.fim:
raise StopIteration
valor = self.atual
self.atual += self.passo
return valor
# Teste
for i in MeuRange(0, 10, 2):
print(i) # 0, 2, 4, 6, 8
4. Iteradores com Estado: Mantendo e Resetando o Progresso
Iteradores com estado podem ser úteis, mas exigem cuidados especiais. Cada chamada a __iter__ deve criar um novo iterador para permitir reinícios.
class LeitorArquivoSimulado:
def __init__(self, linhas):
self.linhas = linhas
def __iter__(self):
# Cria um novo iterador a cada chamada
return IteradorLinha(self.linhas)
class IteradorLinha:
def __init__(self, linhas):
self.linhas = linhas
self.posicao = 0
def __iter__(self):
return self
def __next__(self):
if self.posicao >= len(self.linhas):
raise StopIteration
linha = self.linhas[self.posicao]
self.posicao += 1
return linha
# Demonstração de reinício
dados = ["linha1", "linha2", "linha3"]
leitor = LeitorArquivoSimulado(dados)
print("Primeira iteração:")
for linha in leitor:
print(f" {linha}")
print("Segunda iteração (reinicia corretamente):")
for linha in leitor:
print(f" {linha}")
5. Iteradores Infinitos e Controle de Parada
Iteradores infinitos são poderosos, mas exigem controle explícito para evitar loops infinitos acidentais.
class FibonacciInfinito:
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
valor = self.a
self.a, self.b = self.b, self.a + self.b
return valor
# Uso controlado com limite
fib = FibonacciInfinito()
contador = 0
for numero in fib:
if contador >= 10:
break
print(numero, end=" ") # 0 1 1 2 3 5 8 13 21 34
contador += 1
6. Iteradores vs. Geradores: Quando Usar Cada Um
Geradores oferecem uma sintaxe mais concisa para criar iteradores:
# Versão com gerador
def gerador_fibonacci(limite):
a, b = 0, 1
for _ in range(limite):
yield a
a, b = b, a + b
# Versão com classe iteradora
class ClasseFibonacci:
def __init__(self, limite):
self.limite = limite
self.a, self.b = 0, 1
self.contador = 0
def __iter__(self):
return self
def __next__(self):
if self.contador >= self.limite:
raise StopIteration
valor = self.a
self.a, self.b = self.b, self.a + self.b
self.contador += 1
return valor
# Ambos produzem o mesmo resultado
print(list(gerador_fibonacci(5))) # [0, 1, 1, 2, 3]
print(list(ClasseFibonacci(5))) # [0, 1, 1, 2, 3]
Use geradores para simplicidade e classes iteradoras quando precisar de controle de estado complexo ou herança.
7. Boas Práticas e Armadilhas Comuns
Erro comum 1: Esquecer StopIteration
class IteradorQuebrado:
def __next__(self):
return 42 # Nunca levanta StopIteration - loop infinito!
Erro comum 2: Confundir __iter__ com __getitem__
class ApenasIteravel:
def __getitem__(self, index):
if index >= 5:
raise IndexError
return index * 2
# Funciona com for, mas não é um iterador oficial
for item in ApenasIteravel():
print(item) # 0, 2, 4, 6, 8
Dica de desempenho: Sempre prefira iteradores a listas quando processar grandes volumes de dados, pois iteradores não armazenam toda a sequência em memória.
8. Iteradores na Biblioteca Padrão: Exemplos Reais
Python fornece funções embutidas e módulos que trabalham diretamente com iteradores:
# Funções embutidas iter() e next()
lista = [10, 20, 30]
iterador = iter(lista)
print(next(iterador)) # 10
print(next(iterador)) # 20
# Módulo itertools
from itertools import count, cycle, islice
# count - iterador infinito
for i in islice(count(10, 5), 5):
print(i, end=" ") # 10 15 20 25 30
# cycle - itera infinitamente sobre uma sequência
cores = cycle(["vermelho", "verde", "azul"])
for _ in range(6):
print(next(cores), end=" ") # vermelho verde azul vermelho verde azul
# Depuração com list() e tuple()
iterador_teste = iter([1, 2, 3, 4, 5])
print(list(iterador_teste)) # [1, 2, 3, 4, 5]
Referências
- PEP 234 -- Iterators — Documento oficial que introduziu o protocolo de iteradores em Python
- Python Documentation: Iterator Types — Documentação oficial sobre tipos de iteradores na biblioteca padrão
- Real Python: Python Iterators — Tutorial abrangente sobre iteradores e iteráveis com exemplos práticos
- GeeksforGeeks: Iterators in Python — Guia detalhado sobre criação e uso de iteradores
- Python Documentation: itertools — Documentação oficial do módulo itertools com funções avançadas para iteradores