Se você já usou um loop for em Python, você já usou iteradores. Mas você sabe o que acontece nos bastidores? Os iteradores são um dos pilares da linguagem Python, permitindo que estruturas de dados como listas, tuplas, dicionários e conjuntos sejam percorridas de forma uniforme e eficiente. Entender como eles funcionam é essencial para escrever código Python idiomático e performático.
Neste guia completo sobre iteradores em Python, você vai aprender desde o protocolo de iteração até a criação de iteradores personalizados, explorando o poderoso módulo itertools e entendendo as diferenças cruciais entre iteradores e geradores. Cada conceito é acompanhado de exemplos práticos para fixação.
O Que São Iteradores em Python?
Um iterador é um objeto que implementa o protocolo de iteração, composto por dois métodos: __iter__() e __next__(). O método __iter__() retorna o próprio objeto iterador, enquanto __next__() retorna o próximo valor da sequência. Quando não há mais valores, __next__() levanta a exceção StopIteration para sinalizar o fim da iteração.
Na prática, isso significa que qualquer objeto que possa ser percorrido com um loop for é, de alguma forma, um iterável que produz um iterador. O loop for em Python é, na verdade, um açúcar sintático que chama iter() no objeto e depois next() repetidamente até encontrar StopIteration.
# O que acontece internamente quando você faz:
for item in [1, 2, 3]:
print(item)
# É equivalente a:
iterador = iter([1, 2, 3]) # Chama __iter__() internamente
while True:
try:
item = next(iterador) # Chama __next__() internamente
print(item)
except StopIteration:
break
Fonte: Documentação Oficial Python - Iterators
Iteráveis vs Iteradores: Qual a Diferença?
Essa é uma das dúvidas mais comuns entre desenvolvedores Python. Embora relacionados, iteráveis e iteradores são conceitos distintos:
- Iterável: É todo objeto que pode ser iterado, ou seja, que implementa o método
__iter__()(ou__getitem__()). Listas, tuplas, strings, dicionários e conjuntos são exemplos de iteráveis. Um iterável pode ser usado em um loopfore pode ser convertido em um iterador com a funçãoiter(). - Iterador: É um objeto que implementa ambos
__iter__()e__next__(). Todo iterador é um iterável (porque implementa__iter__()), mas nem todo iterável é um iterador.
A diferença prática mais importante é que um iterador só pode ser percorrido uma vez. Depois que todos os elementos são consumidos, o iterador se esgota e não pode ser reiniciado. Já um iterável pode gerar novos iteradores quantas vezes você quiser.
lista = [1, 2, 3] # lista é um iterável, NÃO é um iterador
iterador1 = iter(lista) # iterador1 é um iterador
iterador2 = iter(lista) # iterador2 é um novo iterador independente
print(next(iterador1)) # 1
print(next(iterador1)) # 2
print(next(iterador1)) # 3
# print(next(iterador1)) # StopIteration - iterador esgotado
print(next(iterador2)) # 1 - iterador2 ainda está no início
Fonte: Glossário Python - Iterable
O Protocolo de Iteração em Detalhe
O protocolo de iteração é a base de todo o sistema de iteração em Python. Vamos detalhar cada parte:
O Método __iter__()
Este método deve retornar um objeto iterador. Em iteráveis como listas, ele retorna um objeto iterador especializado. Em iteradores, ele simplesmente retorna self (o próprio objeto).
O Método __next__()
Este método deve retornar o próximo elemento da sequência. Quando não há mais elementos, deve levantar StopIteration. É isso que sinaliza ao loop for que a iteração terminou.
A Exceção StopIteration
O StopIteration é a forma que o Python usa para comunicar que um iterador foi consumido por completo. Embora você possa usá-la em seu código, é mais comum lidar com ela indiretamente através de loops for.
Fonte: PEP 234 - Iterators
Criando Iteradores Personalizados
Criar seus próprios iteradores é uma habilidade valiosa em Python. Você pode encapsular lógicas de iteração complexas em classes limpas e reutilizáveis. Vamos ver como fazer isso na prática.
Iterador de Números Pares
class Pares:
"""Iterador que retorna números pares até um limite."""
def __init__(self, limite):
self.limite = limite
self.valor = 0
def __iter__(self):
return self
def __next__(self):
if self.valor > self.limite:
raise StopIteration
resultado = self.valor
self.valor += 2
return resultado
# Usando o iterador personalizado
for par in Pares(10):
print(par) # 0, 2, 4, 6, 8, 10
Iterador de Fibonacci
class Fibonacci:
"""Iterador que gera a sequência de Fibonacci."""
def __init__(self, maximo):
self.maximo = maximo
self.a, self.b = 0, 1
self.contador = 0
def __iter__(self):
return self
def __next__(self):
if self.contador >= self.maximo:
raise StopIteration
resultado = self.a
self.a, self.b = self.b, self.a + self.b
self.contador += 1
return resultado
fib = Fibonacci(10)
print(list(fib)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Iterador com Estado Resetável
Diferente de um iterador puro, podemos criar classes que são iteráveis (não iteradores) e produzem um novo iterador a cada chamada de iter(), permitindo que sejam percorridas múltiplas vezes:
class PalavrasTexto:
"""Iterável que produz um novo iterador para cada iteração."""
def __init__(self, texto):
self.texto = texto
def __iter__(self):
return PalavrasIterator(self.texto)
class PalavrasIterator:
def __init__(self, texto):
self.palavras = texto.split()
self.indice = 0
def __iter__(self):
return self
def __next__(self):
if self.indice >= len(self.palavras):
raise StopIteration
resultado = self.palavras[self.indice]
self.indice += 1
return resultado
frase = PalavrasTexto("Python é uma linguagem poderosa")
for p in frase:
print(p) # Python, é, uma, linguagem, poderosa
# Pode percorrer novamente
for p in frase:
print(p.upper()) # PYTHON, É, UMA, LINGUAGEM, PODEROSA
Saiba mais sobre métodos mágicos como __iter__ e __next__ lendo nosso guia completo sobre métodos mágicos em Python.
Fonte: Real Python - Python Iterators and Iterables
Iteradores em Estruturas de Dados Nativas
Cada estrutura de dados em Python implementa a iteração de forma específica. Vamos explorar como funcionam os iteradores para os tipos mais comuns:
Listas e Tuplas
Produzem iteradores que percorrem os elementos na ordem em que foram inseridos. O iterador da lista mantém um índice interno que avança a cada chamada de next().
lista = [10, 20, 30]
it = iter(lista)
print(next(it)) # 10
print(next(it)) # 20
Dicionários
Por padrão, iterar sobre um dicionário percorre suas chaves. Você pode usar .values() para valores e .items() para pares chave-valor.
d = {'a': 1, 'b': 2, 'c': 3}
for chave in d: # Chaves
print(chave)
for valor in d.values(): # Valores
print(valor)
for k, v in d.items(): # Chave e valor
print(f"{k}: {v}")
Strings
Strings são iteráveis e produzem um iterador que percorre cada caractere individualmente, incluindo espaços e caracteres especiais.
for char in "Python":
print(char) # P, y, t, h, o, n
Arquivos
Arquivos são iteráveis em Python! Cada chamada de next() retorna uma linha do arquivo, tornando a leitura de arquivos grandes extremamente eficiente em memória.
with open("dados.txt", "r") as arquivo:
for linha in arquivo: # Lê uma linha por vez
print(linha.strip())
Fonte: Python Wiki - Iterator
Trabalhando com itertools
O módulo itertools é uma biblioteca padrão do Python que fornece funções para criar iteradores avançados. É uma ferramenta indispensável para qualquer desenvolvedor Python que trabalha com iteração. Vamos explorar as funções mais úteis:
count() - Contador Infinito
from itertools import count
for i in count(5): # 5, 6, 7, 8, 9, ...
if i > 10:
break
print(i)
cycle() - Ciclo Infinito
from itertools import cycle
cores = cycle(["vermelho", "azul", "verde"])
for _ in range(6):
print(next(cores)) # vermelho, azul, verde, vermelho, azul, verde
chain() - Encadeando Iteradores
from itertools import chain
lista1 = [1, 2, 3]
lista2 = [4, 5, 6]
for item in chain(lista1, lista2):
print(item) # 1, 2, 3, 4, 5, 6
islice() - Fatiamento de Iteradores
from itertools import islice
# Pega os primeiros 5 elementos de um contador infinito
for i in islice(count(10), 5):
print(i) # 10, 11, 12, 13, 14
product(), permutations(), combinations()
Essas funções geram iteradores matemáticos poderosos para produto cartesiano, permutações e combinações:
from itertools import product, permutations, combinations
# Produto cartesiano
print(list(product([1, 2], ["a", "b"])))
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
# Permutações
print(list(permutations([1, 2, 3], 2)))
# [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
# Combinações
print(list(combinations([1, 2, 3], 2)))
# [(1, 2), (1, 3), (2, 3)]
Fonte: Documentação Oficial - itertools
Iteradores vs Geradores: Quando Usar Cada Um
Esta é uma pergunta frequente em entrevistas técnicas e na prática diária. Tanto iteradores quanto geradores servem para produzir sequências de dados sob demanda, mas cada um tem seu lugar.
Geradores (que usam yield) são uma forma simplificada de criar iteradores. Enquanto um iterador personalizado exige uma classe com os métodos __iter__() e __next__(), um gerador é escrito como uma função normal usando yield. Para a maioria dos casos, geradores são a escolha mais prática.
Iteradores personalizados são mais adequados quando você precisa de:
- Manter estado complexo entre iterações
- Implementar múltiplos métodos além da iteração
- Criar uma abstração rica com comportamento específico
- Reinicialização ou reset do estado (através de um iterável separado do iterador)
# Quando usar gerador (mais simples):
def quadrados(n):
for i in range(n):
yield i ** 2
# Quando usar iterador personalizado (mais controle):
class Quadrados:
def __init__(self, n, prefixo=""):
self.n = n
self.i = 0
self.prefixo = prefixo
def __iter__(self):
return self
def __next__(self):
if self.i >= self.n:
raise StopIteration
resultado = f"{self.prefixo}{self.i ** 2}"
self.i += 1
return resultado
def reset(self):
self.i = 0
Confira também nosso guia completo sobre Python Generators para entender a fundo essa alternativa poderosa aos iteradores.
Fonte: GeeksforGeeks - Iterators in Python
Funções Built-in que Trabalham com Iteradores
Python oferece várias funções nativas que operam diretamente com iteradores:
numeros = [1, 2, 3, 4, 5]
# map - aplica função a cada elemento
dobrados = map(lambda x: x * 2, numeros)
print(list(dobrados)) # [2, 4, 6, 8, 10]
# filter - filtra elementos
pares = filter(lambda x: x % 2 == 0, numeros)
print(list(pares)) # [2, 4]
# zip - agrupa elementos de múltiplos iteráveis
nomes = ["Ana", "Bob", "Eve"]
idades = [25, 30, 28]
for nome, idade in zip(nomes, idades):
print(f"{nome} tem {idade} anos")
# enumerate - enumera elementos com índice
for i, nome in enumerate(nomes, start=1):
print(f"{i}: {nome}")
# reversed - itera na ordem inversa
for num in reversed([1, 2, 3]):
print(num) # 3, 2, 1
# sorted - itera ordenadamente
for num in sorted([3, 1, 2]):
print(num) # 1, 2, 3
# any e all - verificam condições em iteradores
print(any(x > 3 for x in [1, 2, 3, 4])) # True
print(all(x > 0 for x in [1, 2, 3, 4])) # True
Fonte: Programiz - Python Iterators
Boas Práticas com Iteradores
Trabalhar com iteradores de forma eficiente requer atenção a alguns pontos importantes:
1. Consciência do Consumo Único
Lembre-se: iteradores são consumidos uma única vez. Se você precisar percorrer os dados múltiplas vezes, converta o iterador em uma lista (se a memória permitir) ou crie um iterável que produza novos iteradores.
# Problema: iterador consumido após primeiro loop
it = iter([1, 2, 3])
for i in it:
print(i)
# Segundo loop não executa - it já foi consumido
for i in it:
print(i)
# Solução 1: converter para lista
lista = list(iter([1, 2, 3]))
# Solução 2: criar um iterável que gera novos iteradores
class MeusNumeros:
def __iter__(self):
return iter([1, 2, 3])
2. Eficiência com Arquivos Grandes
Aproveite que arquivos são iteráveis para processar grandes volumes sem carregar tudo na memória:
with open("grande_arquivo.csv") as f:
for linha in f:
processar(linha) # Uma linha por vez na memória
3. Generator Expressions vs List Comprehensions
Use generator expressions (com parênteses) em vez de list comprehensions (com colchetes) quando não precisar de todos os valores de uma vez. A economia de memória pode ser enorme.
# List comprehension - cria toda a lista na memória
quadrados_lista = [x**2 for x in range(1000000)]
# Generator expression - produz sob demanda
quadrados_gen = (x**2 for x in range(1000000))
4. Encadeamento de Iteradores
Você pode combinar múltiplos iteradores e funções built-in para criar pipelines de processamento elegantes e eficientes:
dados = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pipeline = (
x
for x in dados
if x % 2 == 0 # filtra pares
)
pipeline = (x * 2 for x in pipeline) # dobra
pipeline = (f"Número: {x}" for x in pipeline) # formata
for item in pipeline:
print(item)
# Número: 4, Número: 8, Número: 12, Número: 16, Número: 20
Fonte: Python Functional Programming HOWTO
Conclusão
Os iteradores em Python são um conceito fundamental que está presente em praticamente todo código Python, muitas vezes sem que você perceba. Desde o simples loop for até pipelines complexos de processamento de dados, o protocolo de iteração é a espinha dorsal que torna tudo possível.
Neste guia, você aprendeu:
- O que são iteradores e como eles se diferenciam de iteráveis
- O protocolo de iteração com
__iter__(),__next__()eStopIteration - Como criar iteradores personalizados com classes
- Como usar o módulo
itertoolspara iteradores avançados - As diferenças entre iteradores e geradores
- Boas práticas para trabalhar com iteradores de forma eficiente
Dominar iteradores vai tornar seu código Python mais idiomático, eficiente e elegante. Pratique criando seus próprios iteradores e explorando o módulo itertools — são ferramentas que você usará constantemente em projetos reais.