Iterator em Python: O que É e Como Funciona | Python Brasil
Entenda iterators em Python: protocolo de iteração, __iter__, __next__, itertools e como criar iteradores personalizados com exemplos reais.
O que é um Iterator?
Um iterator (iterador) é um objeto que implementa o protocolo de iteração do Python, permitindo percorrer uma sequência de valores um por vez. Todo objeto sobre o qual você pode usar um for é iterável, e por trás dos panos, o Python usa iterators para fazer isso funcionar.
Iterators são a espinha dorsal de boa parte do Python: loops for, list comprehensions, funções como map(), filter(), zip() e enumerate() — todas dependem desse protocolo. Entendê-lo profundamente é o que separa um desenvolvedor Python intermediário de um avançado.
O protocolo de iteração
O protocolo exige dois métodos especiais:
__iter__()— retorna o próprio iterador (permite usar o objeto em umfor)__next__()— retorna o próximo valor ou levantaStopIterationquando os itens acabam
# Como o for funciona internamente
numeros = [1, 2, 3]
iterador = iter(numeros) # Chama numeros.__iter__()
print(next(iterador)) # 1 (chama iterador.__next__())
print(next(iterador)) # 2
print(next(iterador)) # 3
# next(iterador) # StopIteration!
# O loop for é equivalente a:
iterador = iter(numeros)
while True:
try:
item = next(iterador)
print(item)
except StopIteration:
break
Iterável vs Iterator
A distinção é fundamental e frequentemente confundida:
lista = [1, 2, 3] # Iterável — tem __iter__(), mas não __next__()
iterador = iter(lista) # Iterator — tem __iter__() E __next__()
# Verificando com duck typing
print(hasattr(lista, '__iter__')) # True
print(hasattr(lista, '__next__')) # False — lista não é iterator
print(hasattr(iterador, '__iter__')) # True
print(hasattr(iterador, '__next__')) # True — iterator é iterável também
# Todo iterator é iterável, mas nem todo iterável é iterator
# Strings, listas, dicts, sets: iteráveis, não iterators
# Objetos retornados por iter(), map(), filter(): iterators
Criando um iterator personalizado
class Contagem:
"""Conta de inicio até fim, inclusivo."""
def __init__(self, inicio, fim, passo=1):
self.inicio = inicio
self.fim = fim
self.passo = passo
self.atual = inicio
def __iter__(self):
return self # O iterator retorna a si mesmo
def __next__(self):
if self.atual > self.fim:
raise StopIteration
valor = self.atual
self.atual += self.passo
return valor
# Usando
for numero in Contagem(1, 10, 2):
print(numero)
# 1, 3, 5, 7, 9
# Também funciona com next(), list(), sum(), etc.
print(list(Contagem(0, 4))) # [0, 1, 2, 3, 4]
print(sum(Contagem(1, 100))) # 5050
iter() com valor sentinela
A função iter() tem uma forma menos conhecida que aceita um callable e um valor sentinela: ela chama o callable repetidamente e para quando o retorno é igual ao sentinela:
# iter(callable, sentinela) — lê até encontrar o valor sentinela
import io
dados = io.StringIO("linha 1\nlinha 2\n\nlinha 3\n")
# Lê linhas até encontrar uma linha vazia
for linha in iter(dados.readline, '\n'):
print(repr(linha))
# 'linha 1\n'
# 'linha 2\n'
# Exemplo prático: ler blocos de arquivo
with open('dados.bin', 'rb') as f:
for bloco in iter(lambda: f.read(4096), b''):
processar(bloco) # processa 4KB por vez, sem carregar o arquivo todo
Iteradores infinitos com itertools
O módulo itertools oferece iterators de alta performance implementados em C. Alguns são infinitos e precisam de islice ou takewhile para serem controlados:
import itertools
# count — conta indefinidamente a partir de um valor
for n in itertools.islice(itertools.count(10, 5), 5):
print(n) # 10, 15, 20, 25, 30
# cycle — repete uma sequência infinitamente
cores = itertools.cycle(['vermelho', 'verde', 'azul'])
for _, cor in zip(range(7), cores):
print(cor) # vermelho, verde, azul, vermelho, verde, azul, vermelho
# repeat — repete um valor N vezes (ou infinitamente)
print(list(itertools.repeat(42, 3))) # [42, 42, 42]
itertools para combinatórias e agrupamento
import itertools
# product — produto cartesiano (equivalente a loops aninhados)
for par in itertools.product('AB', [1, 2]):
print(par) # ('A', 1), ('A', 2), ('B', 1), ('B', 2)
# permutations — todas as permutações
print(list(itertools.permutations('ABC', 2)))
# [('A','B'), ('A','C'), ('B','A'), ('B','C'), ('C','A'), ('C','B')]
# combinations — combinações sem repetição
print(list(itertools.combinations('ABCD', 2)))
# [('A','B'), ('A','C'), ('A','D'), ('B','C'), ('B','D'), ('C','D')]
# groupby — agrupa elementos consecutivos iguais
dados = [('SP', 'Ana'), ('SP', 'Bruno'), ('RJ', 'Carla'), ('RJ', 'Diego')]
for estado, grupo in itertools.groupby(dados, key=lambda x: x[0]):
nomes = [nome for _, nome in grupo]
print(f"{estado}: {nomes}")
# SP: ['Ana', 'Bruno']
# RJ: ['Carla', 'Diego']
# takewhile e dropwhile
numeros = [1, 3, 5, 2, 4, 6]
print(list(itertools.takewhile(lambda x: x % 2 != 0, numeros))) # [1, 3, 5]
print(list(itertools.dropwhile(lambda x: x % 2 != 0, numeros))) # [2, 4, 6]
chain, islice e zip_longest
import itertools
# chain — encadeia múltiplos iteráveis sem criar uma lista intermediária
letras = itertools.chain('ABC', 'DEF', [1, 2, 3])
print(list(letras)) # ['A', 'B', 'C', 'D', 'E', 'F', 1, 2, 3]
# chain.from_iterable — "desachata" um nível de aninhamento
listas = [[1, 2], [3, 4], [5, 6]]
print(list(itertools.chain.from_iterable(listas))) # [1, 2, 3, 4, 5, 6]
# islice — fatia qualquer iterável (até infinitos)
gen_infinito = itertools.count()
print(list(itertools.islice(gen_infinito, 5, 10))) # [5, 6, 7, 8, 9]
# zip_longest — zip que não para no menor iterável
a = [1, 2, 3]
b = ['a', 'b']
print(list(zip(a, b))) # [(1,'a'), (2,'b')]
print(list(itertools.zip_longest(a, b, fillvalue=0))) # [(1,'a'), (2,'b'), (3,0)]
reversed() e reversed
# reversed() funciona em sequências (que têm __len__ e __getitem__)
print(list(reversed([1, 2, 3, 4, 5]))) # [5, 4, 3, 2, 1]
# Para classes personalizadas, implemente __reversed__
class Intervalo:
def __init__(self, inicio, fim):
self.inicio = inicio
self.fim = fim
def __iter__(self):
return iter(range(self.inicio, self.fim + 1))
def __reversed__(self):
return iter(range(self.fim, self.inicio - 1, -1))
iv = Intervalo(1, 5)
print(list(iv)) # [1, 2, 3, 4, 5]
print(list(reversed(iv))) # [5, 4, 3, 2, 1]
Implementando enumerate do zero
Reimplementar funções built-in é um excelente exercício para solidificar o entendimento de iterators:
def meu_enumerate(iteravel, start=0):
"""Reimplementacao de enumerate usando o protocolo de iteracao."""
n = start
for item in iteravel:
yield n, item
n += 1
for indice, valor in meu_enumerate(['a', 'b', 'c'], start=1):
print(indice, valor)
# 1 a
# 2 b
# 3 c
# Implementando zip
def meu_zip(*iteraveis):
iteradores = [iter(it) for it in iteraveis]
while True:
try:
yield tuple(next(it) for it in iteradores)
except StopIteration:
return
Exemplo real: processamento de arquivos grandes
import csv
import itertools
from pathlib import Path
class LeitorCSVGrande:
"""Itera sobre um CSV grande em lotes, sem carregar o arquivo na memória."""
def __init__(self, caminho, tamanho_lote=1000):
self.caminho = Path(caminho)
self.tamanho_lote = tamanho_lote
def __iter__(self):
with self.caminho.open(encoding='utf-8', newline='') as f:
leitor = csv.DictReader(f)
lote = list(itertools.islice(leitor, self.tamanho_lote))
while lote:
yield lote
lote = list(itertools.islice(leitor, self.tamanho_lote))
# Uso
processador = LeitorCSVGrande('clientes.csv', tamanho_lote=500)
for lote in processador:
# Insere 500 registros por vez no banco
banco.inserir_lote(lote)
print(f"Lote de {len(lote)} registros processado.")
Quando usar iterators
- Quando o conjunto de dados é grande demais para caber na memória.
- Quando você precisa de um comportamento de iteração personalizado (ordem, filtragem, transformação).
- Quando implementa APIs que devem ser consumidas de forma incremental (streams, paginação de API).
- Quando quer encadear transformações sem criar estruturas intermediárias.
Erros comuns
# Erro 1: confundir iterável com iterator
lista = [1, 2, 3]
next(lista) # TypeError: 'list' object is not an iterator
next(iter(lista)) # OK — cria um iterator primeiro
# Erro 2: tentar reutilizar um iterator esgotado
it = iter([1, 2, 3])
list(it) # [1, 2, 3]
list(it) # [] — já foi consumido
# Erro 3: modificar uma coleção enquanto itera sobre ela
lista = [1, 2, 3, 4, 5]
for item in lista:
if item % 2 == 0:
lista.remove(item) # Comportamento indefinido!
# Solução: itere sobre uma cópia ou use list comprehension
lista = [item for item in lista if item % 2 != 0]
Boas práticas
- Prefira generators a classes iterator quando a lógica couber em uma função — são mais concisos.
- Use
itertoolsantes de reimplementar algoritmos comuns — a biblioteca padrão é otimizada em C. - Ao criar classes iteráveis, separe o iterável do iterator quando precisar de múltiplos cursores simultâneos.
- Use
iter()com sentinela para leituras de arquivo ou socket que precisam parar em uma condição.
Termos Relacionados
- Generators — Forma simplificada e poderosa de criar iterators
- List Comprehension — Usa iteração internamente para criar listas
- Context Manager — Outro protocolo fundamental do Python