Menu

Geradores em Python: yield, iteração preguiçosa e expressões geradoras

Como geradores produzem valores de forma preguiçosa em Python — a palavra-chave yield, expressões geradoras e quando ganham de uma lista simples.

Uma função que pausa

Um gerador parece com uma função normal, mas em vez de computar um resultado inteiro e retornar, ele produz um valor de cada vez, pausando entre as entregas até quem está pedindo querer o próximo valor.

O mais simples possível:

main.py
Output
Click Run to see the output here.

Repare yield em vez de return. A primeira vez que o for pede um valor, o Python roda o corpo da função até bater em yield 1. A função pausa exatamente ali, entrega 1 de volta ao laço e lembra exatamente onde parou — variáveis e tudo. A próxima iteração pega de onde parou: current += 1, volta para o while, yield 2. E assim por diante até a condição do laço falhar, quando o gerador simplesmente para.

Esse pausa-e-retoma é o truque inteiro.

Por que não só construir uma lista?

Porque a versão lista aloca todos os valores de antemão:

main.py
Output
Click Run to see the output here.

Tranquilo para 5 itens. Agora imagine que você quer 50 milhões de inteiros e só se importa com o primeiro que bate em alguma condição. A versão lista aloca 50 milhões de ints e aí você joga fora a maioria. A versão geradora cria exatamente tantos quantos quem consome usa. Quando o laço for encontra o que quer e dá break, o gerador simplesmente para.

Esse é o padrão que vale internalizar: geradores permitem escrever código de iteração sem decidir de antemão quanto do resultado você vai precisar.

Expressões geradoras

Se você escreveu uma list comprehension, já conhece a sintaxe — troque colchetes por parênteses:

main.py
Output
Click Run to see the output here.

squares_gen não computa nada ainda. É só uma receita. Iterar executa a receita um passo de cada vez.

Expressões geradoras são perfeitas como argumentos para funções que consomem um iterável:

main.py
Output
Click Run to see the output here.

Sem lista intermediária. sum, max e any leem valores um de cada vez, que é exatamente o que querem.

Lendo um arquivo grande, linha por linha

Esse é o caso canônico do mundo real para geradores — processar um arquivo grande demais para carregar na memória:

def parse_log_lines(path):
    with open(path) as f:
        for line in f:
            if line.startswith("ERROR"):
                yield line.rstrip()

for error in parse_log_lines("app.log"):
    print(error)

O arquivo é lido preguiçosamente. Cada chamada ao gerador puxa uma linha do disco, filtra e entrega. O uso de memória fica estável independente do tamanho do arquivo.

Uma passada só

Um gerador tem uma única passagem. Depois que você iterou até o fim, ele está esgotado:

main.py
Output
Click Run to see the output here.

O segundo laço não imprime nada. O gerador não tem mais nada.

Se você precisa iterar mais de uma vez, ou chame a função geradora de novo para um gerador fresco, ou materialize a sequência com list(...) e itere a lista repetidamente. Escolha com base no custo: reconstruir está bem se o trabalho é barato; uma lista está bem se a sequência é pequena.

next() e iteração manual

Você não precisa usar um laço for. next() puxa um valor de cada vez:

main.py
Output
Click Run to see the output here.

StopIteration é como um gerador sinaliza "terminei". Laços for pegam silenciosamente. Em código manual, você pode passar um default para next(gen, default) para evitar a exceção.

Geradores infinitos

Como valores são produzidos sob demanda, um gerador pode representar uma sequência sem fim — contanto que quem consome pare de pedir:

main.py
Output
Click Run to see the output here.

while True com um yield dentro não trava o programa — só significa "se alguém continuar pedindo, continue produzindo". Quem consome decide quando parar.

Esse padrão aparece em dados de streaming, event loops e em qualquer lugar onde você puxa valores de uma fonte que não tem tamanho definido.

yield from: delegando a outro iterável

Se seu gerador quer entregar todo valor de outro iterável, yield from faz numa linha:

main.py
Output
Click Run to see the output here.

Sem yield from você escreveria um laço for aninhado com yield x dentro. Também encaminha chamadas send() e throw() corretamente se você usar — mas no dia a dia, pense nele como "entregue cada valor desta coisa".

Quando recorrer a um gerador

Três sinais de que um gerador é a ferramenta certa:

  1. A sequência é grande, possivelmente infinita, ou cara de produzir por inteiro.
  2. Quem consome pode parar antes do fim (um break na primeira combinação, por exemplo).
  3. Você quer encadear transformações — filtro, map, take — sem construir listas intermediárias.

E quando não:

  • Você precisa de acesso aleatório (seq[42]). Geradores só vão para frente.
  • Você precisa iterar a mesma sequência várias vezes. Use uma lista.
  • A sequência é pequena e você já a tem. Uma list comprehension é mais simples.

Geradores, list comprehensions e listas simples são cada um a resposta certa para trabalhos diferentes. A habilidade é escolher um sem pensar muito — e o jeito mais rápido de desenvolver esse instinto é notar, para cada iteração que você escreve, se "produzir tudo primeiro" ou "produzir um de cada vez" encaixa melhor.

Próxima: context managers em profundidade

Você já viu a maior parte dos idiomas que o Python usa para iteração. Context managers — a instrução with — vêm a seguir, e combinam bem com geradores para streaming de dados de arquivos e conexões de rede.

Perguntas frequentes

O que é um gerador em Python?

Um gerador é uma função que produz valores um de cada vez, pausando entre eles. Você escreve com def como função normal, mas usa yield em vez de return. Chamá-la retorna um objeto gerador; cada iteração de for ou cada chamada a next() roda a função até o próximo yield.

Qual a diferença entre uma lista e um gerador?

Uma lista guarda todo elemento em memória ao mesmo tempo. Um gerador computa elementos sob demanda e esquece depois que são consumidos. Para sequências grandes ou infinitas, geradores usam uma quantidade pequena e fixa de memória; para resultados pequenos que você precisa repetidamente, uma lista é melhor.

Posso iterar um gerador duas vezes?

Não. Um gerador é esgotado depois da primeira passada completa — um segundo laço for sobre ele não produz nada. Se você precisa iterar mais de uma vez, chame a função geradora de novo para pegar um gerador fresco, ou materialize os resultados numa lista.

Aprenda a programar com o Coddy

COMEÇAR