Iteradores em Python
Aprenda como os iteradores Python funcionam, como criar classes iteradoras personalizadas e quando usá-los em vez de listas.
Um iterador é uma das abstrações mais fundamentais do Python. Sempre que você escreve um loop for, chama zip() ou usa uma compreensão de lista, o Python depende silenciosamente do protocolo de iterador por baixo dos panos. Este capítulo explica o que são iteradores, como criar os seus próprios, como usar o rico conjunto de iteradores integrados e quando os iteradores são a ferramenta certa para o trabalho.
O que é um iterador?
O Python distingue dois conceitos relacionados:
- Um iterável é qualquer objeto sobre o qual você pode iterar — uma
list,tuple,str,dict,setou qualquer objeto cuja classe defina__iter__. Ele pode produzir um iterador, mas não rastreia a posição por si mesmo. - Um iterador é um objeto que rastreia o estado de percurso. Ele implementa dois métodos que juntos formam o protocolo de iterador:
__iter__()— retorna o próprio objeto iterador. Isso faz os iteradores funcionarem dentro de loopsfore outros contextos de iteração.__next__()— retorna o próximo valor cada vez que é chamado. Quando não restam valores, lançaStopIteration.
A diferença fundamental: você pode iterar sobre uma lista quantas vezes quiser, porque cada loop for solicita um iterador novo. Um iterador é unidirecional e de uso único — uma vez esgotado, chamar next() sobre ele sempre lança StopIteration.
graph LR
A[Iterator Object] --> B[__iter__]
B --> C[Returns self]
A --> D[__next__]
D --> E[Next Value]
D --> F{No values left?}
F -->|Yes| G[Raises StopIteration]
F -->|No| EComo um loop for usa iteradores
O loop for é apenas açúcar sintático para o protocolo de iterador. Internamente, o Python converte:
for item in some_iterable:
print(item)em algo como isto:
_it = iter(some_iterable) # call __iter__()
while True:
try:
item = next(_it) # call __next__()
except StopIteration:
break
print(item)Compreender essa conversão deixa claro por que qualquer objeto que implementa __iter__ e __next__ funciona perfeitamente em um loop for, com zip(), enumerate() e qualquer outro contexto que espere um iterável.
Criando um iterador personalizado
Para criar um iterador personalizado, defina uma classe que implemente __iter__ e __next__. Aqui está um iterador Countdown que conta regressivamente de um número fornecido até 1:
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self # the iterator is its own iterable
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
for n in Countdown(5):
print(n)
# Output:
# 5
# 4
# 3
# 2
# 1Observe que __iter__ retorna self. É isso que permite que o mesmo objeto seja colocado diretamente em um loop for — o loop chama iter() sobre ele, que chama __iter__(), que retorna o próprio iterador.
Adicionando um parâmetro de passo
Você pode adicionar qualquer lógica dentro de __next__. Aqui está um iterador StepRange que imita range(), mas aceita um valor de passo:
class StepRange:
def __init__(self, start, stop, step=1):
self.current = start
self.stop = stop
self.step = step
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += self.step
return value
print(list(StepRange(0, 10, 3)))
# Output: [0, 3, 6, 9]Chamar list() sobre qualquer iterador o esgota e coleta cada valor em uma lista — um padrão útil quando você precisa de todos os resultados de uma vez.
As funções integradas iter() e next()
As funções integradas iter() e next() são a forma padrão de trabalhar diretamente com o protocolo de iterador.
iter(obj)— chamaobj.__iter__()e retorna o iterador resultante.next(it)— chamait.__next__()e retorna o próximo valor.next(it, default)— retornadefaultem vez de lançarStopIterationquando o iterador está esgotado. Esta é a forma mais segura de espiar o próximo elemento sem um bloco try/except.
words = ["hello", "world"]
it = iter(words)
print(next(it)) # hello
print(next(it)) # world
print(next(it, "done")) # done (exhausted; returns default)A forma com dois argumentos de next() é particularmente útil em cenários de streaming ou análise onde você quer tratar o fim da entrada de forma elegante.
Iteradores são de uso único
Esta é a armadilha mais comum com iteradores: uma vez esgotado, um iterador não pode ser rebobinado.
it = iter([1, 2, 3])
for x in it:
print(x) # prints 1, 2, 3
for x in it:
print(x) # prints nothing — iterator is exhaustedSe você precisa iterar múltiplas vezes, mantenha o iterável original (por exemplo, a lista) e chame iter() novamente, ou use uma compreensão de lista para materializar todos os valores primeiro.
Funções integradas que retornam iteradores
A biblioteca padrão do Python é construída sobre iteradores. Todas essas funções retornam iteradores em vez de listas, portanto são eficientes em memória mesmo sobre sequências muito grandes:
range()
range(start, stop, step) retorna um iterador de inteiros. Ele não armazena os inteiros na memória — calcula cada um sob demanda.
for i in range(1, 6):
print(i)
# Output: 1 2 3 4 5zip()
zip() recebe múltiplos iteráveis e retorna um iterador de tuplas, emparelhando elementos posição a posição. A iteração para na entrada mais curta.
names = ["Alice", "Bob", "Carol"]
scores = [95, 88, 72]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Output:
# Alice: 95
# Bob: 88
# Carol: 72enumerate()
enumerate() envolve qualquer iterável e retorna pares (índice, valor). Use-o para evitar manter uma variável contadora manual.
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
# Output:
# 1. apple
# 2. banana
# 3. cherrymap() e filter()
Ambas as funções retornam iteradores (em Python 3). map(fn, iterable) aplica uma função a cada elemento; filter(fn, iterable) mantém apenas os elementos para os quais a função retorna True.
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
print(doubled) # [2, 4, 6, 8, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4]Verificando se um objeto é um iterador
Use isinstance() com as classes base abstratas do módulo collections.abc para testar a iterabilidade e o status de iterador:
from collections.abc import Iterable, Iterator
my_list = [1, 2, 3]
my_iter = iter(my_list)
print(isinstance(my_list, Iterable)) # True — list is iterable
print(isinstance(my_list, Iterator)) # False — list is NOT an iterator
print(isinstance(my_iter, Iterator)) # True — list_iterator is an iterator
print(isinstance(my_iter, Iterable)) # True — all iterators are also iterablesTodo iterador também é um iterável (porque __iter__ retorna self), mas nem todo iterável é um iterador.
Quando usar iteradores em vez de listas
| Situação | Use |
|---|---|
Precisa de acesso aleatório (items[5]) | list |
| Precisa iterar uma vez, memória é importante | iterador / generator |
| Sequências infinitas ou muito grandes | iterador / generator |
| Precisa iterar múltiplas vezes | list (mantenha o original) |
| Pipeline de transformações | iteradores encadeados (map, filter, itertools) |
Para grandes conjuntos de dados — lendo milhões de linhas de um arquivo, processando dados de streaming — um iterador evita carregar tudo na memória de uma vez. Para coleções pequenas e finitas onde você acessa elementos repetidamente, uma lista é mais simples.
Iteradores vs. Geradores
Um gerador é uma abreviação conveniente para escrever um iterador. Em vez de uma classe com __iter__ e __next__, você escreve uma função que usa yield. O Python a converte em um iterador automaticamente.
# Iterator class
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
# Equivalent generator function
def countdown(start):
while start > 0:
yield start
start -= 1
print(list(countdown(5))) # [5, 4, 3, 2, 1]Use um iterador baseado em classe quando precisar de métodos adicionais ou estado mutável além do que um gerador simples oferece. Use um gerador para a maioria dos outros casos — é mais conciso e igualmente poderoso.
Consulte o capítulo Geradores Python para um tratamento completo de yield, expressões geradoras e send().