# Geradores em Python: yield, iteradores e list comprehensions de listas e dicionários

URL: https://skillaria.top/pt/guides/python/geradores-python-yield

Gerador em Python é um objeto que entrega valores um a um, sob demanda, sem guardar toda a sequência na memória; é criado por uma função com a palavra-chave yield ou por uma expressão geradora entre parênteses. Geradores de listas, dicionários e conjuntos (comprehensions) são uma forma curta de montar a coleção pronta em uma linha: [x * 2 for x in xs], {k: v for k, v in pairs}, {x for x in xs}.

Um gerador em Python é um objeto que entrega valores um de cada vez, apenas quando são solicitados, sem manter toda a sequência na memória. Ele é criado de duas formas: por uma função com a palavra-chave `yield` ou por uma expressão geradora entre parênteses. Ao lado dele estão as list comprehensions, de listas, dicionários e conjuntos — uma forma curta de montar a coleção pronta em uma linha. Vamos ver como o python yield funciona, em que o gerador difere de um iterador e como não transformar uma comprehension em um texto ilegível.

## Iteradores: a base do laço for

Para entender os geradores, é preciso primeiro compreender os iteradores. Um objeto iterável (iterable) é aquilo sobre o que se pode fazer um laço: lista, string, dicionário, arquivo. Um iterador é um objeto que guarda a posição atual e, ao receber `next()`, entrega o próximo elemento; quando os elementos acabam, lança `StopIteration`.

```
nums = [10, 20, 30]
it = iter(nums)      # obtém um iterador a partir da lista
print(next(it))      # 10
print(next(it))      # 20
print(next(it))      # 30
next(it)             # StopIteration
```

O laço `for` faz exatamente isso: chama `iter()` e depois `next()` até o `StopIteration`. Um iterador próprio pode ser escrito como classe com os métodos `__iter__` e `__next__`:

```
class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current  0:
        yield start
        start -= 1

gen = countdown(3)
print(gen)          #
print(next(gen))    # 3
print(list(gen))    # [2, 1] — continuou do ponto em que parou
```

Quando a função chega ao fim (ou a um `return`), o gerador lança sozinho `StopIteration`, e o laço `for` termina sem erros. Daí a diferença principal entre yield e return: o return encerra a função e devolve um único valor, enquanto o yield a suspende e pode entregar quantos valores forem necessários.

### Gerador e iterador: diferenças

Todo gerador é um iterador: ele possui `__iter__` e `__next__`. Mas nem todo iterador é um gerador. A diferença está na forma de criação e nos recursos.

|  | Iterador (classe) | Gerador |
| --- | --- | --- |

| Como criar | classe com `__iter__` e `__next__` | função com `yield` ou `(x for x in ...)` |

| Estado | manualmente, em atributos | guardado automaticamente nas variáveis locais |

| StopIteration | lançado por você | aparece sozinho ao sair da função |

| Recursos extras | o que você implementar | `send()`, `throw()`, `close()` |

Ambos têm uma propriedade importante em comum: é possível percorrê-los só uma vez. Um novo `for` sobre um gerador esgotado não produz nada — é preciso criar outro.

### Gerador infinito e leitura de arquivos grandes

O gerador não precisa terminar. Uma sequência infinita é segura desde que você pegue dela um número limitado de elementos:

```
from itertools import islice

def ids():
    n = 1
    while True:
        yield f"ID-{n:04d}"
        n += 1

print(list(islice(ids(), 3)))  # ['ID-0001', 'ID-0002', 'ID-0003']
```

O cenário mais prático é processar um arquivo grande linha por linha. Os geradores podem ser encadeados em um pipeline, e na memória haverá a cada momento uma única linha, e não o arquivo inteiro:

```
def read_lines(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.rstrip("\n")

def only_errors(lines):
    for line in lines:
        if "ERROR" in line:
            yield line

for line in only_errors(read_lines("app.log")):
    print(line)
```

### yield from e send

`yield from` repassa todos os valores de outro objeto iterável, dispensando um laço aninhado:

```
def flatten(groups):
    for g in groups:
        yield from g

print(list(flatten([[1, 2], [3], [4, 5]])))  # [1, 2, 3, 4, 5]
```

O método `send()` envia um valor para dentro do gerador: ele se torna o resultado da expressão `yield`. Antes do primeiro `send` o gerador precisa ser «iniciado» com `next()`. Assim se escreve, por exemplo, um acumulador de média:

```
def averager():
    total, count = 0, 0
    avg = None
    while True:
        value = yield avg
        total += value
        count += 1
        avg = total / count

a = averager()
next(a)            # chega ao primeiro yield
print(a.send(10))  # 10.0
print(a.send(20))  # 15.0
```

No código de aplicação o `send` aparece raramente, mas é um tema frequente em entrevistas de emprego.

### range é um gerador?

Não. O `range` é uma sequência preguiçosa: também não guarda todos os números, mas permite indexação, `len()`, verificação com `in` e percorrer quantas vezes for preciso. O gerador não faz nada disso.

```
r = range(5)
print(r[2], len(r), 3 in r)  # 2 5 True
print(list(r), list(r))      # nas duas vezes [0, 1, 2, 3, 4]
```

## List comprehension: gerador de listas

A list comprehension tem a forma `[expressão for x in coleção]`. Ela cria uma nova lista e não altera a original. Lê-se da esquerda para a direita: primeiro «o que colocar», depois «de onde tirar».

```
amounts = [100, 250, 400]

# laço comum
with_markup = []
for a in amounts:
    with_markup.append(a * 1.2)

# a mesma coisa com comprehension
with_markup = [a * 1.2 for a in amounts]
print(with_markup)  # [120.0, 300.0, 480.0]
```

Apesar do nome «gerador de listas», o resultado é uma lista pronta, e não um gerador: todos os elementos são calculados imediatamente.

### Lista com if e if else

É aqui que mais se confunde. A condição-filtro fica no fim e descarta elementos. A condição-escolha (operador ternário) fica no início e transforma cada elemento em um dentre dois valores, sem descartar nada.

```
amounts = [100, 250, 400]

big = [a for a in amounts if a > 200]                    # [250, 400]
labels = ["muito" if a > 200 else "pouco" for a in amounts]
# ['pouco', 'muito', 'muito']

# filtro e escolha juntos
odd_marks = ["par" if n % 2 == 0 else "ímpar" for n in range(6) if n > 2]
# ['ímpar', 'par', 'ímpar']
```

A escrita `[a for a in amounts if a > 200 else 0]` é erro de sintaxe: o `else` só existe na condição do início.

### Comprehensions aninhadas e lista bidimensional

O duplo `for` é escrito na mesma ordem dos laços aninhados, de cima para baixo. Isso ajuda a não trocar a ordem ao juntar uma lista de listas:

```
groups = [[1, 2], [3], [4, 5]]
flat = [x for g in groups for x in g]
# equivalente a:
# for g in groups:
#     for x in g:
#         flat.append(x)
print(flat)  # [1, 2, 3, 4, 5]
```

Uma matriz (lista bidimensional) é criada com uma comprehension dentro de outra. O erro clássico é multiplicar a lista: as linhas acabam sendo o mesmo objeto.

```
bad = [[0] * 3] * 2
bad[0][0] = 1
print(bad)   # [[1, 0, 0], [1, 0, 0]] — as duas linhas mudaram

grid = [[0] * 3 for _ in range(2)]
grid[0][0] = 1
print(grid)  # [[1, 0, 0], [0, 0, 0]]

# transposição
m = [[1, 2, 3], [4, 5, 6]]
print([[row[i] for row in m] for i in range(3)])  # [[1, 4], [2, 5], [3, 6]]
```

## Gerador de dicionário e de conjunto

O mesmo recurso funciona para outras coleções. Chaves com dois-pontos montam um dicionário (dict comprehension), e chaves sem dois-pontos — um conjunto (set comprehension).

```
spent = [("comida", 500), ("táxi", 300), ("comida", 200)]

categories = {cat for cat, _ in spent}
print(categories)  # {'comida', 'táxi'} — a ordem em conjuntos não é garantida

last = {cat: amount for cat, amount in spent}
print(last)        # {'comida': 200, 'táxi': 300}

squares = {n: n ** 2 for n in range(4)}  # {0: 0, 1: 1, 2: 4, 3: 9}
swapped = {v: k for k, v in squares.items()}
```

Repare em `last`: quando a chave se repete, vale o último valor, e a soma de 500 se perdeu. Se for preciso somar, a comprehension não serve — escreva um laço ou use `collections.Counter` / `defaultdict`.

### E a comprehension de tuplas?

Uma «tuple comprehension» separada em Python não existe. Os parênteses criam uma expressão geradora, e a tupla é obtida passando-a para `tuple()`:

```
t = tuple(n * 2 for n in range(3))  # (0, 2, 4)
```

## Expressão geradora: não guardar, mas entregar

Se trocar os colchetes por parênteses, obtém-se uma expressão geradora. Ela não calcula nada antecipadamente e entrega os valores um a um. Para um milhão de elementos, a diferença de memória é enorme:

```
import sys

as_list = [n for n in range(1_000_000)]
as_gen = (n for n in range(1_000_000))
print(sys.getsizeof(as_list))  # cerca de 8 MB
print(sys.getsizeof(as_gen))   # algumas centenas de bytes
```

Expressões geradoras costumam ficar dentro de funções agregadoras. Se o gerador for o único argumento, os parênteses extras podem ser omitidos. `any()` e `all()`, além disso, param na primeira resposta e não percorrem o restante.

```
spent = [("comida", 500), ("táxi", 300), ("comida", 200)]

total = sum(a for _, a in spent)            # 1000
has_big = any(a > 400 for _, a in spent)    # True, o resto não é verificado
biggest = max(spent, key=lambda p: p[1])    # ('comida', 500)
```

A regra de escolha é simples: se o resultado for necessário várias vezes, ou para indexação e `len()`, use lista. Se for preciso percorrer uma vez e calcular algo, use gerador.

## Escopo: a variável não vaza

Pergunta que pega até programadores experientes:

```
x = 10
squares = [x for x in range(3)]
print(x)  # 10
```

A variável do laço dentro da comprehension vive no próprio escopo e não sobrescreve a externa (no Python 3 é assim; no Python 2 era diferente). Num laço `for` comum, depois dele `x` valeria 2. A partir da versão 3.12 (PEP 709) as comprehensions são embutidas na função externa e funcionam mais rápido, mas a regra de visibilidade continuou a mesma.

## Desempacotamento * e ** em comprehensions: novidade do Python 3.15 (PEP 798)

O asterisco para desempacotamento já podia ser usado havia tempos em chamadas de função e em literais de lista, mas dentro de uma comprehension era `SyntaxError`. No Python 3.15 essa proibição foi retirada: `*elemento` significa «coloque aqui todos os elementos dele». Funciona em listas, conjuntos e geradores, inclusive assíncronos.

```
# Python 3.15+
groups = [[1, 2], [3], [4, 5]]
flat = [*g for g in groups]        # [1, 2, 3, 4, 5]
uniq = {*g for g in groups}        # conjunto
lazy = (*g for g in groups)        # gerador preguiçoso
```

O duplo asterisco faz o mesmo com dicionários. Isso é cômodo para configurações em camadas «padrão → arquivo → flags», em que cada camada seguinte sobrescreve as chaves da anterior.

```
# Python 3.15+
layers = [{"debug": False, "port": 80}, {"port": 8080}, {"debug": True}]
config = {**layer for layer in layers}
# {'debug': True, 'port': 8080}
```

Duas ressalvas do curso. O asterisco não pode ser combinado com uma expressão condicional sem parênteses: `[*w if w else 0 for w in weeks]` é erro de sintaxe, e a condição precisa estar entre parênteses. E esse código não roda no 3.14 ou em versões anteriores: se a biblioteca suporta versões antigas, mantenha o duplo `for` ou o `itertools.chain.from_iterable`.

## Geradores assíncronos

Se uma função `async def` contém `yield`, obtém-se um gerador assíncrono. Percorre-se com `async for`, e dentro dele é possível aguardar a rede ou o disco com `await`. Também existem comprehensions assíncronas.

```
import asyncio

async def ticks(n):
    for i in range(n):
        await asyncio.sleep(0.1)
        yield i

async def main():
    result = [i async for i in ticks(3)]
    print(result)  # [0, 1, 2]

asyncio.run(main())
```

## Erros típicos

- **Comprehension de três andares.** Dois `for`, dois `if` e um ternário numa linha ninguém lê daqui a uma semana. Se não cabe numa linha legível, escreva um laço comum: a PEP 8 dá mais valor à legibilidade do que à brevidade.
- **Nova passagem pelo gerador.** Um segundo `sum(gen)` devolve 0, porque o gerador já está esgotado.
- **Comprehension por efeito colateral.** `[print(x) for x in xs]` cria uma lista desnecessária de `None`. Para ações use um laço `for`.
- **Esperar uma lista de uma expressão geradora.** `(x for x in xs)[0]` dá `TypeError`: o gerador não tem índices.

## Exercícios com geradores para praticar

1. Escreva um gerador `fib()` que produza infinitamente os números de Fibonacci e pegue os 10 primeiros com `islice`.
2. Em uma única linha obtenha um dicionário «palavra → tamanho» para as palavras com mais de três letras.
3. Calcule a soma dos quadrados dos números pares até um milhão sem criar uma lista.
4. Escreva um gerador `chunks(seq, n)` que devolva pedaços da lista de `n` elementos.

## Resumo

O iterador entrega os elementos um a um por meio de `next()`, e o gerador é a forma mais simples de escrever um: uma função com python yield ou uma expressão entre parênteses. Os geradores de listas, dicionários e conjuntos montam a coleção pronta em uma linha: o filtro `if` vai no fim, a escolha `if else` — no início. Precisa economizar memória ou processar um fluxo de dados uma única vez — use gerador; precisa do resultado várias vezes — use lista. Comprehensions, geradores e o novo desempacotamento do Python 3.15 são praticados no curso, no exemplo do controle de gastos.

## FAQ

**Dá para usar return dentro de um gerador?**

Sim. Um return sem valor apenas encerra o gerador; um return com valor o coloca no atributo value da exceção StopIteration, e esse valor é recebido pela expressão yield from.

**Como transformar um gerador em lista?**

Passe-o para list(): list(gen). Depois disso o gerador estará esgotado, e todos os valores ficarão na memória.

**O que é mais rápido: list comprehension ou laço com append?**

Em geral a comprehension é um pouco mais rápida, pois não chama o método append em cada iteração. Mas a escolha deve considerar a legibilidade, e a diferença raramente é decisiva.

**Como saber se um objeto é um gerador?**

Use inspect.isgenerator(obj) para o objeto ou inspect.isgeneratorfunction(f) para a função; para verificar se é um iterador, use isinstance(obj, collections.abc.Iterator).

**Qual a diferença entre expressão geradora e list comprehension?**

Os colchetes constroem de imediato a lista inteira na memória; os parênteses criam um gerador, que calcula os elementos sob demanda e pode ser percorrido apenas uma vez.


---
Source: Skillaria.Top (https://skillaria.top)
