W3docs

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, set ou 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 loops for e outros contextos de iteração.
    • __next__() — retorna o próximo valor cada vez que é chamado. Quando não restam valores, lança StopIteration.

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.

python— editable, runs on the server
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| E

Como 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
# 1

Observe 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) — chama obj.__iter__() e retorna o iterador resultante.
  • next(it) — chama it.__next__() e retorna o próximo valor.
  • next(it, default) — retorna default em vez de lançar StopIteration quando 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 exhausted

Se 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  5

zip()

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: 72

enumerate()

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. cherry

map() 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 iterables

Todo 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çãoUse
Precisa de acesso aleatório (items[5])list
Precisa iterar uma vez, memória é importanteiterador / generator
Sequências infinitas ou muito grandesiterador / generator
Precisa iterar múltiplas vezeslist (mantenha o original)
Pipeline de transformaçõesiteradores 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().

Prática

Prática
Which methods make up the Python iterator protocol?
Which methods make up the Python iterator protocol?
Was this page helpful?