Grande parte das estruturas de dados em Python, como listas, tuplas, conjuntos, dicionários e arquivos, pode ser percorrida elemento por elemento. Esse processo é conhecido como iteração.
Quando utilizamos um laço for, normalmente não pensamos em como ele obtém cada elemento da coleção. Entretanto, por trás desse mecanismo existe um conceito fundamental da linguagem: os iteradores (iterators).
Um iterador é um objeto capaz de fornecer seus elementos um de cada vez, permitindo percorrer uma coleção sem precisar carregar todos os seus elementos simultaneamente em memória.
Os iteradores são a base de diversas funcionalidades do Python, como laços de repetição, geradores (generators), compreensões (comprehensions), funções como map() e filter(), leitura de arquivos e processamento de grandes volumes de dados.
Neste capítulo estudaremos como funcionam os iteradores, o protocolo de iteração do Python, como criar iteradores personalizados e suas aplicações práticas.
Um iterador é um objeto que fornece elementos de uma coleção um por vez, conforme eles são solicitados.
Visualmente:
Coleção
↓
Iterador
↓
Elemento 1
↓
Elemento 2
↓
Elemento 3
↓
Fim da coleção
Em vez de disponibilizar todos os elementos de uma só vez, o iterador entrega cada item sob demanda.
Esses dois conceitos costumam ser confundidos.
É qualquer objeto que pode ser percorrido.
Exemplos:
- Listas;
- Tuplas;
- Strings;
- Conjuntos;
- Dicionários;
- Arquivos.
Exemplo:
nomes = ["Ana", "Carlos", "Pedro"]A lista é um iterável.
É o objeto responsável por percorrer o iterável.
Podemos obtê-lo utilizando a função iter().
nomes = ["Ana", "Carlos", "Pedro"]
iterador = iter(nomes)
print(iterador)Os elementos são obtidos utilizando next().
nomes = ["Ana", "Carlos", "Pedro"]
iterador = iter(nomes)
print(next(iterador))
print(next(iterador))
print(next(iterador))Resultado:
Ana
Carlos
Pedro
Quando todos os elementos são consumidos, ocorre uma exceção.
next(iterador)Resultado:
StopIteration
Essa exceção informa que não existem mais elementos disponíveis.
Quando escrevemos:
for numero in [1, 2, 3]:
print(numero)O Python faz algo semelhante a:
iterador = iter([1, 2, 3])
while True:
try:
numero = next(iterador)
print(numero)
except StopIteration:
breakO laço for utiliza iteradores automaticamente.
Para que um objeto seja considerado um iterador, ele deve implementar dois métodos especiais:
__iter__()__next__()
Retorna o próprio iterador.
def __iter__(self):
return selfRetorna o próximo elemento da sequência.
Quando não houver mais elementos, deve lançar:
StopIteration
class Contador:
def __init__(self, limite):
self.atual = 1
self.limite = limite
def __iter__(self):
return self
def __next__(self):
if self.atual > self.limite:
raise StopIteration
numero = self.atual
self.atual += 1
return numeroUso:
contador = Contador(5)
for numero in contador:
print(numero)Resultado:
1
2
3
4
5
Após percorrer um iterador completamente, ele fica esgotado.
numeros = iter([1, 2, 3])
for n in numeros:
print(n)
for n in numeros:
print(n)A segunda repetição não exibirá nada.
Podemos utilizar o módulo collections.abc.
from collections.abc import Iterable
print(isinstance([1, 2, 3], Iterable))
print(isinstance(10, Iterable))Resultado:
True
False
from collections.abc import Iterator
lista = [1, 2, 3]
iterador = iter(lista)
print(isinstance(lista, Iterator))
print(isinstance(iterador, Iterator))Resultado:
False
True
Arquivos também são iteráveis.
with open("dados.txt") as arquivo:
for linha in arquivo:
print(linha.strip())Cada linha é lida sob demanda, sem carregar todo o arquivo em memória.
Considere uma lista com um milhão de números.
numeros = list(range(1_000_000))Todos os números ficam armazenados na memória.
Agora:
numeros = iter(range(1_000_000))Os elementos são produzidos conforme necessário.
Esse comportamento reduz significativamente o consumo de memória.
class Alfabeto:
def __init__(self):
self.letra = ord("A")
def __iter__(self):
return self
def __next__(self):
if self.letra > ord("Z"):
raise StopIteration
atual = chr(self.letra)
self.letra += 1
return atual
for letra in Alfabeto():
print(letra)Saída:
A
B
C
...
Z
- A classe implementa o protocolo de iteração.
__iter__()retorna o próprio objeto.__next__()fornece uma letra por vez.- Ao ultrapassar
"Z", lançaStopIteration. - O laço
forconsome automaticamente o iterador.
| Lista | Iterador |
|---|---|
| Armazena todos os elementos | Produz elementos sob demanda |
| Pode ser percorrida várias vezes | Geralmente é consumido apenas uma vez |
| Maior consumo de memória | Menor consumo de memória |
| Acesso direto por índice | Apenas acesso sequencial |
Errado:
lista = [1, 2, 3]
next(lista)Resultado:
TypeError
Correto:
iterador = iter(lista)
next(iterador)Ao criar iteradores personalizados, o método __next__() deve lançar StopIteration quando não houver mais elementos.
Após percorrer completamente um iterador, ele precisa ser recriado.
Um iterador deve implementar tanto __iter__() quanto __next__().
- Utilize o laço
forsempre que possível, pois ele gerencia os iteradores automaticamente. - Utilize
next()apenas quando precisar controlar manualmente a iteração. - Crie iteradores personalizados apenas quando houver necessidade específica.
- Prefira iteradores para processamento de grandes volumes de dados.
- Lance corretamente
StopIterationem implementações próprias.
Leitura de arquivos muito grandes linha por linha.
Processamento de milhões de registros sem carregar tudo na memória.
Consumo paginado de grandes conjuntos de dados.
Processamento contínuo de páginas coletadas.
Leitura incremental de conjuntos de dados extensos.
Processamento de eventos em tempo real.
| Iterable | Iterator |
|---|---|
| Pode ser percorrido | Percorre os elementos |
Implementa __iter__() |
Implementa __iter__() e __next__() |
| Ex.: lista, tupla, string | Ex.: resultado de iter(lista) |
| Pode gerar um novo iterador | Mantém o estado da iteração |
Os iteradores são objetos responsáveis por percorrer coleções elemento por elemento, entregando os dados sob demanda. Eles seguem o protocolo de iteração, implementando os métodos __iter__() e __next__(), e são utilizados internamente pelo laço for e por diversas funcionalidades da linguagem. Compreender a diferença entre iteráveis e iteradores é fundamental para trabalhar com processamento eficiente de dados, especialmente em aplicações que manipulam grandes volumes de informações ou utilizam leitura incremental de arquivos e fluxos de dados.
- Utilize
iter()enext()para percorrer manualmente uma lista de números. - Crie um iterador para uma string e exiba seus caracteres um a um utilizando
next(). - Reescreva um laço
forutilizando explicitamenteiter(),next()etry/except StopIteration. - Implemente uma classe
Contadorque produza números de 1 até um limite informado. - Crie um iterador que percorra os meses do ano.
- Desenvolva um iterador que gere apenas números pares até um valor máximo.
- Utilize
collections.abcpara verificar se diferentes objetos são iteráveis e/ou iteradores. - Leia um arquivo de texto utilizando um iterador e conte quantas linhas ele possui.
- Crie um iterador personalizado que percorra uma lista de produtos retornando apenas seus nomes.
- Pesquise a relação entre iteradores e generators e explique por que geradores também são considerados iteradores.
Desenvolva um programa chamado Sistema de Iteração Personalizada.
O sistema deverá implementar uma classe chamada Biblioteca, responsável por armazenar uma coleção de livros.
Cada livro deve possuir:
- Título;
- Autor;
- Ano de publicação.
A classe Biblioteca deve implementar o protocolo de iteração para permitir percorrer os livros utilizando um laço for.
- Adicionar livros à biblioteca.
- Percorrer todos os livros utilizando um iterador personalizado.
- Exibir cada livro formatado durante a iteração.
- Permitir reiniciar a iteração criando um novo iterador.
- Tratar corretamente o fim da coleção utilizando
StopIteration.
- Implemente os métodos
__iter__()e__next__(). - Não utilize listas diretamente no laço principal; utilize o iterador personalizado.
- Organize o código em classes e métodos reutilizáveis.
- Documente os métodos utilizando docstrings.
- Utilize tratamento de exceções quando necessário.
Implemente um segundo iterador para a biblioteca que permita percorrer os livros em ordem inversa, do mais recentemente adicionado para o primeiro, sem alterar a coleção original.
Esse desafio integra conceitos de iteradores, protocolo de iteração, programação orientada a objetos, tratamento de exceções, coleções, laços de repetição e organização de código, demonstrando como o mecanismo de iteração do Python funciona internamente e como ele pode ser personalizado para diferentes necessidades.