---
title: "Geradores e Iteradores em Python"
url: "https://python.dev.br/blog/geradores-iteradores-python/"
markdown_url: "https://python.dev.br/blog/geradores-iteradores-python.MD"
description: "Entenda como funcionam geradores e iteradores em Python. Tutorial completo com yield, generator expressions, itertools e casos de uso práticos."
date: "2026-03-23"
author: "Equipe python.dev.br"
---

# Geradores e Iteradores em Python

Entenda como funcionam geradores e iteradores em Python. Tutorial completo com yield, generator expressions, itertools e casos de uso práticos.


Quando você percorre uma lista com um `for` em Python, está usando o protocolo de iteração sem perceber. Por trás dos panos, Python chama métodos especiais para obter um elemento de cada vez. Entender como **iteradores** e **geradores** funcionam é essencial para escrever código eficiente, especialmente quando lidamos com grandes volumes de dados.

## O Que São Iteradores

Um iterador é qualquer objeto que implementa o **protocolo de iteração**: os métodos `__iter__()` e `__next__()`. O método `__iter__` retorna o próprio objeto iterador, e `__next__` retorna o próximo elemento ou levanta `StopIteration` quando não há mais elementos.

```python
# Listas são iteráveis, mas não são iteradores
numeros = [1, 2, 3]

# iter() cria um iterador a partir de um iterável
iterador = iter(numeros)

print(next(iterador))  # 1
print(next(iterador))  # 2
print(next(iterador))  # 3
# next(iterador)       # StopIteration!
```

Quando você escreve `for item in lista`, o Python internamente chama `iter()` na lista para obter um iterador e depois chama `next()` repetidamente até receber `StopIteration`.

## Criando um Iterador Customizado

Você pode criar seus próprios iteradores implementando `__iter__` e `__next__` em uma classe:

```python
class Contagem:
    """Iterador que conta de inicio até fim."""

    def __init__(self, inicio: int, fim: int):
        self.atual = inicio
        self.fim = fim

    def __iter__(self):
        return self

    def __next__(self) -> int:
        if self.atual > self.fim:
            raise StopIteration
        valor = self.atual
        self.atual += 1
        return valor

# Usando o iterador customizado
for num in Contagem(1, 5):
    print(num, end=" ")  # 1 2 3 4 5
```

Essa abordagem funciona, mas exige bastante código. Para a maioria dos casos, **geradores** oferecem uma alternativa muito mais concisa.

## O Que São Geradores

Geradores são funções que usam a palavra-chave `yield` em vez de `return`. Quando chamadas, elas retornam um objeto gerador que implementa automaticamente o protocolo de iteração — sem que você precise escrever `__iter__` e `__next__` manualmente.

```python
def contagem(inicio: int, fim: int):
    """Gerador que conta de inicio até fim."""
    atual = inicio
    while atual <= fim:
        yield atual
        atual += 1

# Usando o gerador — mesmo resultado, menos código
for num in contagem(1, 5):
    print(num, end=" ")  # 1 2 3 4 5
```

A diferença fundamental é que o `yield` **pausa** a execução da função e **retorna** o valor. Na próxima chamada a `next()`, a execução **continua** exatamente de onde parou, preservando todo o estado local (variáveis, posição no loop, etc.).

## yield vs return: Diferenças Fundamentais

A diferença entre `yield` e `return` vai além da sintaxe:

```python
# Com return — executa tudo de uma vez, retorna lista completa
def quadrados_lista(n: int) -> list[int]:
    resultado = []
    for i in range(n):
        resultado.append(i ** 2)
    return resultado

# Com yield — produz um valor por vez, sob demanda
def quadrados_gerador(n: int):
    for i in range(n):
        yield i ** 2

# A lista ocupa memória proporcional a n
lista = quadrados_lista(1_000_000)  # ~8MB na memória

# O gerador ocupa memória constante, independente de n
gerador = quadrados_gerador(1_000_000)  # ~120 bytes na memória
```

Com `return`, a função calcula todos os valores, armazena em uma lista e retorna tudo de uma vez. Com `yield`, cada valor é calculado apenas quando solicitado. Essa é a essência da **lazy evaluation** (avaliação preguiçosa).

## Generator Expressions vs List Comprehensions

Assim como existem [list comprehensions](/blog/compreensao-de-listas-python/), existem **generator expressions** — a versão lazy das comprehensions:

```python
# List comprehension — cria lista inteira na memória
soma_lista = sum([x ** 2 for x in range(1_000_000)])

# Generator expression — calcula sob demanda
soma_gerador = sum(x ** 2 for x in range(1_000_000))

# Ambos dão o mesmo resultado, mas o gerador usa muito menos memória
```

A sintaxe é quase idêntica: troque os colchetes `[]` por parênteses `()`. Quando a generator expression é o único argumento de uma função, você pode omitir os parênteses extras, como no exemplo com `sum()` acima.

Use generator expressions quando você precisa iterar sobre os valores apenas uma vez. Use list comprehensions quando precisa acessar os elementos múltiplas vezes ou por índice.

## Lazy Evaluation e Eficiência de Memória

A principal vantagem dos geradores é a **eficiência de memória**. Em vez de carregar todos os dados na memória de uma vez, o gerador produz um valor por vez:

```python
import sys

# Comparando uso de memória
lista = [i for i in range(1_000_000)]
gerador = (i for i in range(1_000_000))

print(f"Lista: {sys.getsizeof(lista):,} bytes")     # ~8,448,728 bytes
print(f"Gerador: {sys.getsizeof(gerador):,} bytes")  # ~200 bytes
```

Isso é especialmente importante quando se trabalha com [manipulação de arquivos grandes](/blog/manipulacao-de-arquivos-python/) ou [processamento de dados](/blog/python-para-ciencia-de-dados/). Um gerador permite processar arquivos de gigabytes sem estourar a memória.

## yield from para Delegação de Geradores

O `yield from` delega a iteração para outro iterável ou gerador, simplificando geradores compostos:

```python
def numeros_pares(n: int):
    for i in range(0, n, 2):
        yield i

def numeros_impares(n: int):
    for i in range(1, n, 2):
        yield i

def todos_os_numeros(n: int):
    yield from numeros_pares(n)
    yield from numeros_impares(n)

# Produz: 0, 2, 4, 6, 8, 1, 3, 5, 7, 9
for num in todos_os_numeros(10):
    print(num, end=" ")
```

Sem `yield from`, você precisaria de um loop `for` explícito para cada sub-gerador. O `yield from` também propaga exceções e valores de retorno corretamente, o que é útil em geradores mais complexos.

## send() e close(): Geradores como Coroutines

Geradores não são apenas produtores de dados — eles também podem **receber** dados via `send()`:

```python
def acumulador():
    """Gerador que acumula valores recebidos via send()."""
    total = 0
    while True:
        valor = yield total
        if valor is None:
            break
        total += valor

gen = acumulador()
next(gen)           # Inicializa o gerador, retorna 0

print(gen.send(10))  # 10 (total acumulado)
print(gen.send(20))  # 30
print(gen.send(5))   # 35

gen.close()  # Encerra o gerador
```

O método `send()` envia um valor para dentro do gerador, que é recebido como resultado da expressão `yield`. O `close()` levanta `GeneratorExit` dentro do gerador, permitindo que ele finalize recursos.

Essa capacidade de enviar e receber dados é a base das **coroutines** em Python e influenciou diretamente o design do [async/await](/blog/python-async-await/).

## Casos de Uso Práticos

### Ler Arquivos Grandes Linha a Linha

```python
def ler_linhas(caminho: str):
    """Lê um arquivo grande sem carregar tudo na memória."""
    with open(caminho, "r", encoding="utf-8") as f:
        for linha in f:
            linha = linha.strip()
            if linha:  # ignora linhas vazias
                yield linha

# Processa um arquivo de 10GB usando memória constante
for linha in ler_linhas("logs_servidor.txt"):
    if "ERROR" in linha:
        print(linha)
```

### Pipeline de Processamento de Dados

```python
def ler_csv(caminho: str):
    with open(caminho) as f:
        next(f)  # pula o cabeçalho
        for linha in f:
            yield linha.strip().split(",")

def filtrar_ativos(registros):
    for registro in registros:
        if registro[3] == "ativo":
            yield registro

def extrair_nomes(registros):
    for registro in registros:
        yield registro[1]

# Pipeline: lê -> filtra -> extrai — sem carregar tudo na memória
pipeline = extrair_nomes(filtrar_ativos(ler_csv("clientes.csv")))
for nome in pipeline:
    print(nome)
```

### Séries Infinitas

```python
def fibonacci():
    """Gerador infinito da sequência de Fibonacci."""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Pega apenas os 10 primeiros números de Fibonacci
from itertools import islice
print(list(islice(fibonacci(), 10)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
```

## itertools: Ferramentas para Iteração Avançada

O módulo `itertools` da biblioteca padrão oferece geradores poderosos para manipulação de iteráveis:

```python
from itertools import chain, islice, count, cycle, combinations, groupby

# chain — concatena múltiplos iteráveis
letras = chain("abc", "def", "ghi")
print(list(letras))  # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

# islice — fatia iteradores (como slice, mas para iteráveis)
naturais = count(1)  # 1, 2, 3, 4, ...
print(list(islice(naturais, 5)))  # [1, 2, 3, 4, 5]

# cycle — repete um iterável infinitamente
cores = cycle(["vermelho", "verde", "azul"])
print([next(cores) for _ in range(7)])
# ['vermelho', 'verde', 'azul', 'vermelho', 'verde', 'azul', 'vermelho']

# combinations — todas as combinações possíveis
print(list(combinations([1, 2, 3, 4], 2)))
# [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
```

O `itertools` é essencial para quem trabalha com [estruturas de dados](/blog/estruturas-de-dados-python/) e precisa de operações eficientes sobre sequências.

## Boas Práticas: Quando Usar Geradores vs Listas

Use **geradores** quando:

- Os dados são grandes demais para caber na memória
- Você só precisa iterar sobre os valores uma vez
- Está construindo pipelines de processamento de dados
- Precisa de séries infinitas ou sob demanda
- Performance de memória é prioridade

Use **listas** quando:

- Precisa acessar elementos por índice (`lista[3]`)
- Precisa iterar múltiplas vezes sobre os mesmos dados
- Precisa saber o tamanho com `len()`
- O conjunto de dados é pequeno e cabe confortavelmente na memória
- Precisa modificar elementos (append, insert, sort)

Uma regra prática: se você está apenas iterando com `for` e não precisa reusar os dados, um gerador é quase sempre a melhor escolha.

## Conclusão

Iteradores e geradores são ferramentas fundamentais para escrever código Python eficiente e elegante. Os iteradores definem o protocolo que torna o `for` loop tão poderoso. Os geradores, com `yield`, simplificam a criação de iteradores e permitem processar dados sob demanda, economizando memória. Combinados com generator expressions e `itertools`, eles formam um arsenal completo para manipulação de dados em qualquer escala — de scripts simples a pipelines de dados corporativos.

> **Iteradores em outras linguagens**: <a href="https://rustlang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust</a> é famosa por seus iteradores zero-cost — composições como `.filter().map().collect()` são otimizadas pelo compilador sem overhead de memória. Já <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go</a> introduziu recentemente o range-over-func, trazendo iteradores customizados para a linguagem de forma idiomática.
