Geradores Python e rendimento explicado: Economize memória com exemplos reais
Os geradores produzem valores um de cada vez, em vez de construir uma lista inteira na memória. Observe exatamente como o rendimento pausa uma função, veja a diferença de memória que medimos e crie um pequeno pipeline de dados.
- Uma função contendo
yieldretorna um gerador, e seu corpo não é executado até que você solicite um valor. yieldpausa a função e mantém seu estado local até a próxima solicitação.- Os geradores usam uma quantidade pequena e fixa de memória, independentemente da quantidade de valores que produzam.
- Um gerador pode ser consumido apenas uma vez, o que é a armadilha mais comum.
Imagine carregar um arquivo de log de 10 GB em uma lista apenas para encontrar os erros. Seu programa pode ficar sem memória antes de encontrar uma. Geradores Python resolvem isso produzindo valores sob demanda, um de cada vez, para que você possa trabalhar com dados enormes ou até mesmo intermináveis quase sem memória.
Este guia mostra o que realmente acontece quando um gerador funciona, passo a passo. Executamos cada exemplo no Python 3.13 e a saída vem dessa execução. Ajuda já estar confortável com funções e loops, que nosso tutorial Python para iniciantes cobre.
O que é um gerador?
Um gerador é um tipo especial de iterador que você cria escrevendo uma função usando yield. Qualquer função com yield torna-se automaticamente uma função geradora, e chamá-la fornece um objeto gerador em vez de executar o código.
def count_up(limit):
print("starting")
n = 1
while n <= limit:
yield n
n += 1
print("finished")
gen = count_up(2)
print(gen)
# <generator object count_up at 0x...>
Observe que “iniciando” não foi impresso. Chamar a função criou apenas o gerador. Nada lá dentro foi executado ainda.
Como funciona o rendimento, passo a passo
Cada chamada para next() executa a função até atingir yield, devolve esse valor e congela no lugar. A próxima chamada é retomada logo após essa linha, com todas as variáveis locais intactas.
print(next(gen))
# starting
# 1
print(next(gen))
# 2
next(gen)
# finished
# StopIteration
Siga a saída com cuidado. “starting” aparece apenas no primeiro next(). No segundo, a execução continua de onde parou, então n ainda está disponível e se torna 2. No terceiro, o loop termina, “terminado” é impresso, a função cai no final e o Python aumenta StopIteration para sinalizar que não resta mais nada.
Você raramente liga para next() sozinho. Um loop for faz isso por você e lida com StopIteration silenciosamente, assim como list(), sum() e funções semelhantes:
print(list(count_up(3)))
# starting
# finished
# [1, 2, 3]
rendimento vs retorno
| Aspecto | retornar |
rendimento |
|---|---|---|
| O que isso faz | Termina a função | Pausa a função |
| Valores produzidos | Um resultado | Uma série, uma de cada vez |
| Estado local | Descartado | Preservado entre valores |
| Quando o código é executado | Imediatamente quando chamado | Somente quando um valor é solicitado |
| Memória para muitos valores | Deve segurar todos eles | Segura um de cada vez |
Esse atraso é conhecido como avaliação preguiçosa: os valores são calculados apenas quando necessário. Um simples return dentro de um gerador simplesmente o encerra, o que aciona StopIteration.
A diferença de memória, medida
Comparamos uma lista de um milhão de quadrados com um gerador equivalente, usando sys.getsizeof:
import sys
squares_list = [n * n for n in range(1_000_000)]
squares_gen = (n * n for n in range(1_000_000))
print(sys.getsizeof(squares_list), sys.getsizeof(squares_gen))
# 8448728 200
A própria estrutura da lista ocupava cerca de 8,4 MB, enquanto o gerador ocupava apenas 200 bytes em nosso sistema. A lacuna real é ainda maior, pois getsizeof não conta os objetos inteiros dentro da lista. O tamanho do gerador também permanece o mesmo para um bilhão de itens, pois armazena apenas o seu estado atual e não os seus resultados.
Há uma compensação, no entanto. Uma lista pode ser reutilizada, indexada e medida com len(). Um gerador não oferece nada disso. Se você precisar de acesso aleatório ou fizer vários loops em uma pequena quantidade de dados, uma lista será mais simples. Consulte nosso guia para lista Python vs tupla vs conjunto para saber mais sobre esses tipos.
Expressões geradoras
Para casos simples, você não precisa de uma função. Uma expressão geradora se parece com uma compreensão de lista com colchetes no lugar de quadrados:
squares = [n * n for n in range(5)] # builds a list now
squares = (n * n for n in range(5)) # produces values on demand
print(sum(n * n for n in range(5)))
# 30
Quando uma expressão geradora é o único argumento de uma função, como em sum(), você pode omitir os colchetes extras. Este é um hábito organizado para totais de dados grandes, porque nenhuma lista temporária é criada.
Crie um pipeline de dados
É aqui que os geradores realmente brilham. Pequenos geradores podem ser encadeados para que cada item flua através de cada etapa, um de cada vez:
def read_lines(lines):
for line in lines:
yield line.strip()
def only_errors(lines):
for line in lines:
if line.startswith("ERROR"):
yield line
def messages(lines):
for line in lines:
yield line.split(" ", 1)[1]
log = ["INFO started\n", "ERROR db down\n",
"ERROR disk full\n", "INFO stopped\n"]
print(list(messages(only_errors(read_lines(log)))))
# ['db down', 'disk full']
Cada etapa realiza um pequeno trabalho e pode ser testada por conta própria. No uso real, troque a lista de amostras por um arquivo aberto, porque os objetos de arquivo podem ser lidos linha por linha, como mostra nosso guia para manipulação de arquivos Python. Mesmo um log de vários gigabytes passaria com muito pouca memória.
Dica: Um pipeline não funciona até que algo o consuma. Construir a cadeia é instantâneo. O processamento acontece apenas quando você faz um loop no gerador final ou o envolve em uma chamada como list().
Sequências infinitas
Uma lista não pode ser infinita, mas um gerador pode, porque só calcula o próximo valor. Use itertools.islice para pegar quantos você precisar:
import itertools
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
print(list(itertools.islice(fibonacci(), 8)))
# [0, 1, 1, 2, 3, 5, 8, 13]
Porém, tenha cuidado: chamar list(fibonacci()) sem limite seria executado até que sua memória acabasse. O mesmo padrão é adequado para geradores de ID, loops de pesquisa e resultados de API paginados, onde você busca a próxima página somente quando a atual acabar.
rendimento de e lotes
yield from passa para outro iterável, economizando um loop aninhado:
def chain_all(*groups):
for group in groups:
yield from group
print(list(chain_all([1, 2], (3, 4))))
# [1, 2, 3, 4]
Um padrão muito prático é dividir os dados em lotes de tamanho fixo, por exemplo, para inserir mil linhas em um banco de dados por vez:
def batches(items, size):
batch = []
for item in items:
batch.append(item)
if len(batch) == size:
yield batch
batch = []
if batch:
yield batch
print(list(batches(range(7), 3)))
# [[0, 1, 2], [3, 4, 5], [6]]
O final if batch garante que um grupo menor de sobras não seja perdido. Python 3.12 e posteriores também incluem itertools.batched, que faz um trabalho semelhante.
enviar, fechar e limpar
Os geradores também podem receber valores. send() retoma o gerador e o valor se torna o resultado da expressão yield. Você deve chamar next() uma vez primeiro para alcançar o primeiro yield:
def running_total():
total = 0
while True:
value = yield total
total += value
t = running_total()
next(t)
print(t.send(10), t.send(5))
# 10 15
A maioria das pessoas raramente precisa disso. Uma preocupação mais comum é a limpeza. Se você parar mais cedo, um bloco finally ainda será executado quando o gerador estiver fechado:
def tracked():
try:
yield 1
yield 2
finally:
print("cleanup ran")
tr = tracked()
next(tr)
tr.close()
# cleanup ran
O Pytest usa exatamente essa ideia: um fixture com yield executa sua limpeza posteriormente, como nosso tutorial pytest demonstra. Da mesma forma, o decorador contextmanager da biblioteca padrão transforma um gerador em um bloco with.
Geradores em aulas
Escrever um método __iter__ como gerador é a maneira mais fácil de tornar sua própria classe iterável. Um bônus é que cada loop recebe um novo gerador, para que o objeto possa ser repetido repetidamente:
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
n = self.start
while n > 0:
yield n
n -= 1
c = Countdown(3)
print(list(c), list(c))
# [3, 2, 1] [3, 2, 1]
Quando usar um gerador
| Use um gerador quando | Use uma lista quando |
|---|---|
| Os dados são grandes ou seu tamanho é desconhecido | Os dados são pequenos e cabem na memória |
| Você processa itens uma vez, em ordem | Você faz loop várias vezes |
| Você pode parar mais cedo | Você precisa de len(), indexação ou fatiamento |
| Você está lendo um stream ou arquivos | Você precisa classificar ou reverter os dados |
| Você está construindo um pipeline | A simplicidade importa mais do que a memória |
Um gerador nem sempre é mais rápido no geral. Se você acabar usando todos os valores, o trabalho total será semelhante, e a compreensão da lista pode ser ainda mais rápida para dados pequenos porque tem menos sobrecarga por item. Os principais benefícios são o uso da memória e a obtenção dos primeiros resultados mais rapidamente.
Erros comuns
Reutilizando um gerador exausto
Isso pega quase todo mundo em algum momento. Um gerador lembra que está concluído:
g = count_up(2)
print(list(g), list(g))
# [1, 2] []
A segunda chamada obtém uma lista vazia sem nenhum erro. Chame a função novamente para obter um novo gerador ou use a abordagem de classe mostrada acima.
Esperando que o código seja executado imediatamente
def noisy():
print("body runs")
yield 1
n = noisy() # nothing is printed
print(next(n)) # body runs, then 1
As verificações de argumentos no topo de um gerador não são executadas até o primeiro next(), portanto, os erros podem aparecer longe da chamada que os causou.
Outras armadilhas
- Chamada
len()ou indexação. Os geradores não suportam nenhum dos dois e geram umTypeError. - Fechar um arquivo muito cedo. Retornar um gerador de dentro de um bloco
withpode significar que o arquivo fecha antes que qualquer linha seja lida. Coloque oyielddentro do blocowith. - Envolver tudo em
list(). Isso acaba com o benefício da memória. - Usando um para dados pequenos. Uma lista simples geralmente é mais clara.
Considerações finais
Um gerador é uma função que pode pausar. Essa ideia oferece baixo uso de memória, sequências infinitas e pipelines limpos. Para colocá-lo em prática, encontre um local em seu código que construa uma lista grande apenas para percorrê-la uma vez e transforme-o em um gerador. Em seguida, tente encadear dois pequenos geradores em um arquivo de log real.
Perguntas frequentes
O que o rendimento faz em Python?
yield produz um valor e pausa a função, mantendo suas variáveis locais. A função continua do mesmo ponto quando o próximo valor é solicitado.
Qual é a diferença entre um gerador e uma lista?
Uma lista armazena todos os seus valores na memória de uma só vez. Um gerador calcula cada valor sob demanda, usa pouca memória e geralmente pode ser consumido apenas uma vez.
Qual é a diferença entre um iterador e um gerador?
Todo gerador é um iterador. Um gerador é simplesmente a maneira mais fácil de escrever um, já que Python cria os métodos __iter__ e __next__ para você.
Você pode fazer um loop em um gerador duas vezes?
Não. Uma vez esgotado, não rende mais nada, sem gerar erro. Crie um novo gerador chamando a função novamente ou armazene os resultados em uma lista.
Os geradores são mais rápidos que as listas?
Nem sempre. Eles usam muito menos memória e entregam o primeiro resultado mais cedo, mas a compreensão da lista pode ser um pouco mais rápida quando você consome inteiramente pequenas quantidades de dados.
Continue aprendendo
Precisa de ajuda com testes, WordPress ou SEO?
Ajudamos as equipes a fornecer software melhor e a expandir seus sites. Conte-nos no que você está trabalhando e sugeriremos um próximo passo claro.
Mais guias sobre testes, IA e desenvolvimento moderno
Gostou deste artigo? Escolha um tópico e vá direto para nossos tutoriais mais recentes.
Explicação das alucinações de IA: por que os modelos de linguagem inventam as coisas e como reduzi-las
Explicação das alucinações de IA: o que são, por que os modelos de linguagem as produzem, tipos comuns, como reduzi-las com aterramento e instruções…
Leia o artigo →DevOpsVariáveis de ambiente explicadas: arquivos .env, segredos e configuração bem feitos
Variáveis de ambiente explicadas com exemplos de Python que executamos: leitura e configuração, por que cada valor é uma string, arquivos .env, hera…
Leia o artigo →Desenvolvimento de softwareExplicação da limitação de taxa de API: algoritmos, cabeçalhos e tratamento de erros 429
Limitação de taxa de API explicada com uma simulação que executamos: token bucket, janela fixa, janela deslizante, 429 respostas, cabeçalhos de limi…
Leia o artigo →Automação de testesZombando em Python: unittest.mock, patch e os erros que fazem os testes mentirem
Zombaria em Python com unittest.mock, testado: Mock e return_value, side_effect, patch onde um nome é usado, especificação e autospec, zombaria de d…
Leia o artigo →Teste de softwareCobertura de código explicada: cobertura de linha versus filial e o que 100% realmente significa
Cobertura de código explicada com uma execução cobertura.py real: cobertura de linha, ramal e condição, leitura de um relatório, por que 100% de cob…
Leia o artigo →Bancos de dados e SQLDELETE vs TRUNCATE vs DROP em SQL: diferenças, reversão e quando usar cada um
DELETE vs TRUNCATE vs DROP explicado com SQL testado: o que cada um remove, rollback, aciona, redefinição de identidade, velocidade e como PostgreSQ…
Leia o artigo →Bancos de dados e SQLExibições SQL explicadas: crie, use e evite as armadilhas, com exemplos
Visualizações SQL explicadas com consultas que executamos: criação de uma visualização, resultados ao vivo, ocultação de colunas, desempenho, visual…
Leia o artigo →Desenvolvimento de softwareInterface TypeScript vs tipo: diferenças reais, testadas com o compilador
Interface TypeScript versus tipo, verificada com o compilador real: o que ambos podem fazer, o que apenas um pode fazer, mesclagem de declarações, e…
Leia o artigo →Desenvolvimento de softwarePython *args e **kwargs explicados: funções flexíveis com exemplos claros
Python *args e **kwargs explicados com o código que executamos: empacotamento e descompactação, ordem dos parâmetros, argumentos somente de palavras…
Leia o artigo →Desenvolvimento de softwareFunções Python Lambda: sintaxe, usos reais e quando não usá-los
Funções lambda do Python explicadas com o código que executamos: sintaxe, classificação com chave, mapa e filtro, fechamentos, armadilha de ligação …
Leia o artigo →Inteligência artificialO que são agentes de IA? Como eles funcionam, com um loop de agente simples que você pode executar
O que são agentes de IA? Um guia simples para o loop do agente, ferramentas, memória e proteções, com um pequeno loop Python que executamos, riscos …
Leia o artigo →DevOpsMonitoramento versus observabilidade: a verdadeira diferença, com logs, métricas e rastreamentos
Explicação do monitoramento versus observabilidade: falhas conhecidas e desconhecidas, logs, métricas e rastreamentos, um passo a passo do incidente…
Leia o artigo →DevOpsDocker Volumes vs Bind Mounts: persista os dados do contêiner da maneira certa
Volumes Docker versus montagens de ligação explicadas: por que os dados do contêiner desaparecem, volumes nomeados, montagens de ligação, tmpfs, exe…
Leia o artigo →Automação de testesTDD vs BDD: principais diferenças, um recurso criado de ambas as maneiras e quando usar cada um
TDD vs BDD explicado com um recurso de desconto construído em ambos os sentidos: refatoração vermelho-verde com o código que executamos, cenários da…
Leia o artigo →Teste de softwareTeste de aceitação do usuário (UAT): processo, lista de verificação e exemplos reais
Explicação do teste de aceitação do usuário: quem o executa, como ele difere do controle de qualidade, um processo de sete etapas, um modelo de caso…
Leia o artigo →Bancos de dados e SQLValores SQL NULL explicados: IS NULL, COALESCE, NULLIF e as armadilhas a serem evitadas
Valores SQL NULL explicados com consultas que executamos: lógica de três valores, IS NULL, COALESCE, NULLIF, como agregações, junções e classificaçã…
Leia o artigo →Bancos de dados e SQLInstrução SQL CASE WHEN: sintaxe, exemplos reais e erros comuns
SQL CASE WHEN explicado com consultas que executamos: formulários simples e pesquisados, ordem de condição, pivôs com SUM, truques COUNT, classifica…
Leia o artigo →Desenvolvimento de softwareO que é JWT? JSON Web Tokens explicados com um exemplo prático
O que é JWT? JSON Web Tokens explicados: as três partes, um token construído e adulterado no código que executamos, JWT vs sessões, armazenamento, t…
Leia o artigo →Desenvolvimento de softwareLoop de eventos JavaScript explicado: pilha de chamadas, microtarefas e temporizadores com exemplos
O loop de eventos JavaScript explicado com o código que executamos: pilha de chamadas, filas de tarefas e microtarefas, por que setTimeout 0 não é i…
Leia o artigo →Desenvolvimento de softwareGeradores Python e rendimento explicado: economize memória com exemplos reais
Geradores Python e rendimento explicados com o código que executamos: como eles pausam e reiniciam, medem economia de memória, pipelines, sequências…
Leia o artigo →Inteligência artificialO que são incorporações? Similaridade de vetores e cossenos explicada de forma simples
O que são incorporações? Um guia em linguagem simples para vetores, similaridade de cossenos e pesquisa semântica, com um pequeno exemplo que execut…
Leia o artigo →DevOpsImplantação azul-verde vs canário: diferenças, compensações e como escolher
Explicação da implantação azul-verde versus canário: como cada um funciona, custo, risco e reversão, agrupamento estável de usuários que testamos, m…
Leia o artigo →Automação de testesTestes instáveis: o que os causa e como corrigi-los para sempre
Testes instáveis explicados com demonstrações que executamos: as principais causas, como confirmar e diagnosticar uma, soluções adequadas para tempo…
Leia o artigo →Teste de softwareTeste de carga versus teste de estresse: diferenças, métricas e exemplos k6
Teste de carga versus teste de estresse explicado: o que cada um prova, testes de pico e absorção, as métricas que importam, por que as médias engan…
Leia o artigo →Teste de softwareCaso de teste versus cenário de teste: principais diferenças, exemplos e quando usar cada um
Caso de teste vs cenário de teste explicado: definições claras, uma tabela lado a lado, um recurso de redefinição de senha retirado de cenários para…
Leia o artigo →Bancos de dados e SQLOtimização de consultas SQL: leia planos de execução e corrija consultas lentas, com intervalos
Otimização de consulta SQL testada em um milhão de linhas: leia planos de execução, adicione os índices corretos, corrija filtros não sargáveis, use…
Leia o artigo →Bancos de dados e SQLSQL UNION vs UNION ALL: diferenças, exemplos e desempenho testado
SQL UNION vs UNION ALL explicado com consultas que executamos: duplicatas, as regras que ambos seguem, um bug de dados real, desempenho medido, INTE…
Leia o artigo →Desenvolvimento de softwareTutorial da API JavaScript Fetch: GET, POST, tratamento de erros e tempos limite
Um tutorial da API JavaScript Fetch testado em um servidor local: GET e POST, por que 404 não lança, um auxiliar reutilizável, strings de consulta, …
Leia o artigo →Desenvolvimento de softwareManipulação de arquivos Python: leitura, gravação, CSV, JSON e pathlib com exemplos
Manipulação de arquivos Python explicada com código que executamos: modos abertos, com, leitura de arquivos grandes, CSV, JSON, pathlib, arquivos bi…
Leia o artigo →Desenvolvimento de softwareClasses e objetos Python: tutorial OOP com exemplos reais
Classes e objetos Python explicados com código que executamos: __init__, métodos, atributos de classe vs instância, herança, propriedades, métodos d…
Leia o artigo →Inteligência artificialO que é um modelo de linguagem grande? Como funcionam os LLMs, explicado sem exageros
O que é um modelo de linguagem grande? LLMs explicados em linguagem simples: tokens, incorporações, atenção, treinamento, temperatura e janelas de c…
Leia o artigo →DevOpsComandos Linux para iniciantes: o guia essencial do terminal com exemplos
Comandos Linux para iniciantes, agrupados por tarefa: navegação, arquivos, visualização de logs, grep, pipes, permissões, processos e arquivos, com …
Leia o artigo →Automação de testesSelenium vs Playwright: principais diferenças, código comparado e qual escolher
Selenium vs Playwright comparado: arquitetura, espera, suporte a navegador e linguagem, depuração, o mesmo teste em ambas as ferramentas, conselhos …
Leia o artigo →Teste de softwareParticionamento de equivalência: técnica de design de teste explicada com exemplos resolvidos
Explicação do particionamento de equivalência: como encontrar partições válidas e inválidas, exemplos resolvidos para idades, senhas e cupons, teste…
Leia o artigo →Teste de softwareGravidade vs Prioridade em Testes de Software: Diferenças, Matriz e Exemplos Reais
Explicação da gravidade versus prioridade em testes de software: definições claras, uma matriz de quatro vias com exemplos, escalas de nível, quem d…
Leia o artigo →Bancos de dados e SQLTutorial de subconsultas SQL: tipos, subconsultas correlacionadas e EXISTS com exemplos
Um tutorial de subconsultas SQL com consultas que executamos: subconsultas escalares, IN, FROM e SELECT, subconsultas correlacionadas, EXISTS, a arm…
Leia o artigo →Bancos de dados e SQLTransações SQL e ACID explicados: COMMIT, ROLLBACK e níveis de isolamento
Transações SQL e ACID explicadas com uma transferência bancária que executamos: COMMIT, ROLLBACK, savepoints, locks, níveis de isolamento, leituras …
Leia o artigo →Desenvolvimento de softwareGit merge vs rebase: diferenças, exemplos e quando usar cada um
Git merge vs rebase explicado com saída real do log, uma tabela comparativa, a regra de ouro do rebase e um fluxo de trabalho seguro para equipes.
Leia o artigo →Desenvolvimento de softwareList comprehension em Python: sintaxe, exemplos e quando evitar
List comprehension em Python explicada: sintaxe, filtros, if-else, laços aninhados, tempos medidos e quando um laço for comum é a melhor escolha.
Leia o artigo →Bancos de dados e SQLSQL JOIN explicado com exemplos (Inner, Left, Right, Full)
Entenda os joins SQL passo a passo com tabelas e consultas de exemplo: INNER, LEFT, RIGHT, FULL, CROSS e SELF, além de erros comuns e dicas de desem…
Leia o artigo →