Voltar ao Glossario
Glossario Python

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 um for)
  • __next__() — retorna o próximo valor ou levanta StopIteration quando 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 itertools antes 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