Módulo 7 - Geradores e iteradores avançados

O protocolo do iterador por dentro

9 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

Velocidade

O que você vai aprender

  • Distinguir um iterável de um iterador.
  • Entender o papel de __iter__, __next__ e StopIteration.
  • Ver o que o laço for faz por baixo dos panos.
  • Criar uma classe iteradora do zero.

Iterável e iterador não são a mesma coisa

A palavra iteração esconde dois papéis que vale separar. O iterável é o objeto que você percorre, como uma lista; ele sabe produzir, sob demanda, um iterador. O iterador é quem realmente entrega os valores um a um. A distinção fica clara com a função iter: chamar iter em uma lista devolve um iterador novo, separado da lista. A lista é a fonte; o iterador é o ponteiro que caminha por ela. Um iterável pode gerar vários iteradores independentes; cada um mantém a própria posição.

numeros = [10, 20, 30]        # a lista e um iteravel

it = iter(numeros)            # iter() devolve um iterador
print(next(it))               # 10
print(next(it))               # 20
print(next(it))               # 30
print(next(it))               # levanta StopIteration

iter() cria o iterador; next() pede o próximo valor; no fim vem StopIteration.

O ciclo é sempre esse: iter obtém um iterador, next pede o próximo valor, e quando não há mais nada, o próximo next levanta a exceção StopIteration. Repare que StopIteration não é um erro no sentido usual; é um sinal combinado que significa acabou. Entender que iterável e iterador são coisas diferentes evita confusões comuns, como esperar que um iterador esgotado volte ao começo. Ele não volta: uma vez percorrido, precisa ser recriado a partir do iterável.

O que o for faz nos bastidores

Com o protocolo em mente, o laço for perde o mistério. Quando você escreve for x in colecao, o Python faz três coisas: chama iter na coleção para obter um iterador, chama next nesse iterador repetidamente para pegar cada valor, e para quando um next levanta StopIteration, capturando essa exceção por você. Ou seja, o for é açúcar sintático sobre iter, next e um try, except invisível. Escrever o equivalente à mão deixa isso evidente.

colecao = ["a", "b", "c"]

# Isto:
for item in colecao:
    print(item)

# E equivalente a isto por baixo dos panos:
it = iter(colecao)
while True:
    try:
        item = next(it)
    except StopIteration:
        break
    print(item)

O for é iter + next repetido + captura de StopIteration, embutidos.

Criando um iterador próprio

Agora você pode criar seus próprios objetos iteráveis. A forma completa é uma classe com dois métodos: o __iter__, que devolve o iterador (muitas vezes o próprio objeto), e o __next__, que entrega o próximo valor ou levanta StopIteration quando acaba. O exemplo abaixo é uma contagem regressiva. Repare que ela guarda a posição atual em um atributo e a atualiza a cada chamada de __next__, exatamente como um ponteiro que avança.

class Regressiva:
    def __init__(self, inicio):
        self.atual = inicio

    def __iter__(self):
        return self            # o proprio objeto e o iterador

    def __next__(self):
        if self.atual <= 0:
            raise StopIteration  # sinaliza o fim
        valor = self.atual
        self.atual -= 1
        return valor

for n in Regressiva(3):
    print(n)                   # 3, 2, 1

Uma classe iteradora: __iter__ devolve self, __next__ entrega ou encerra com StopIteration.

Funciona, mas é bastante código para algo simples. Guarde essa sensação, porque é justamente ela que os geradores resolvem: na próxima aula você vai ver que uma função com yield produz o mesmo iterador com uma fração das linhas, cuidando de __iter__, __next__ e StopIteration automaticamente. Entender o protocolo à mão primeiro é o que faz o gerador deixar de ser mágica e virar uma abreviação compreensível de algo que você já sabe montar.

Teste rápido

Como um iterador sinaliza que não há mais valores a entregar?

Perguntas frequentes

Qual a diferença prática entre iterável e iterador?
O iterável é a fonte que você percorre (uma lista, por exemplo) e sabe criar iteradores com __iter__. O iterador é o ponteiro que caminha pela sequência, entregando valores com __next__ e guardando a posição atual. Você pode criar vários iteradores independentes de um mesmo iterável; cada um mantém a própria posição e, uma vez esgotado, não reinicia.
StopIteration é um erro de verdade?
Tecnicamente é uma exceção, mas o papel dela é ser um sinal combinado de fim, não uma falha. O for a captura automaticamente e para o laço sem barulho. Você só a vê explicitamente quando chama next à mão além do fim da sequência. Não é algo para tratar com preocupação; é como a iteração diz acabou.
Por que um iterador esgotado não volta ao início?
Porque o iterador guarda uma posição que só avança. Quando chega ao fim, ele fica esgotado: novos next continuam levantando StopIteration. Para percorrer de novo, você pede um iterador novo ao iterável com iter (ou usa o for de novo sobre o iterável, que faz isso por você). A fonte permanece; o ponteiro é descartável.
O __iter__ precisa devolver self?
No caso de um iterador que é o próprio objeto, sim, devolver self é a convenção. Mas um iterável que não é o próprio iterador (como uma lista) devolve um objeto iterador separado no __iter__. A regra é: todo iterador tem __iter__ devolvendo a si mesmo, para poder ser usado em um for diretamente.
Quando vale a pena escrever a classe iteradora inteira?
Raramente, na prática. A maioria dos casos fica melhor com um gerador (função com yield), que produz o mesmo comportamento com muito menos código. A classe compensa quando você precisa de muito estado, métodos extras ou controle fino sobre o objeto. Entender a classe, porém, é essencial para saber o que o gerador faz por você.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.