Geradores são funções que produzem valores um de cada vez.
Eles usam yield em vez de return para pausar a execução.
Primeiramente, geradores economizam memória para grandes sequências.
Por exemplo, range(1000000) é um gerador, não uma lista.
Além disso, geradores podem produzir sequências infinitas.
A voz passiva é usada aqui: “os valores são gerados sob demanda, não antecipadamente”.
Quando utilizar geradores? Em processamento de grandes arquivos.
Também em fluxos de dados infinitos e pipelines eficientes.
Python oferece funções geradoras (yield) e expressões geradoras.
Vamos explorar ambos os tipos com exemplos práticos.
Três subtítulos guiarão você pelo universo dos geradores.
Ao final, você economizará memória e processamento significativamente.
Funções geradoras com yield
Uma função geradora usa yield em vez de return.
Ela retorna um objeto gerador, não uma lista completa.
Cada yield pausa a função e guarda seu estado.
Quando usar funções geradoras? Em sequências complexas.
Por exemplo, Fibonacci, números primos ou leitura de arquivos grandes.
A voz passiva é aplicada: “o estado da função é preservado entre chamadas”.
Exemplo de funções geradoras:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# Gerador simples de números pares def gerar_pares(limite): """Gera números pares até o limite (usando yield).""" for i in range(limite): if i % 2 == 0: yield i print("=== Funções Geradoras ===") pares = gerar_pares(10) print(f"Gerador: {pares}") print(f"Valores: {list(pares)}") # Gerador de Fibonacci (sequência infinita) def fibonacci(): """Gera sequência de Fibonacci infinitamente.""" a, b = 0, 1 while True: yield a a, b = b, a + b print("\nPrimeiros 10 Fibonacci:") fib = fibonacci() for _ in range(10): print(next(fib), end=" ") print() # Gerador para ler arquivo linha a linha def ler_linhas_arquivo(caminho): """Lê arquivo linha por linha (economiza memória).""" with open(caminho, 'r') as arquivo: for linha in arquivo: yield linha.rstrip('\n') # Criando arquivo de teste with open("teste_gerador.txt", "w") as f: for i in range(1000): f.write(f"Linha {i}\n") print("\nPrimeiras 5 linhas do arquivo:") for i, linha in enumerate(ler_linhas_arquivo("teste_gerador.txt")): if i >= 5: break print(f" {linha}") # Gerador com estado (usando send) def acumulador(): """Recebe valores via send e acumula.""" total = 0 while True: valor = yield total if valor is not None: total += valor print("\nAcumulador com send:") acc = acumulador() next(acc) # Inicializa o gerador print(f"Enviando 5: {acc.send(5)}") print(f"Enviando 10: {acc.send(10)}") print(f"Enviando 3: {acc.send(3)}") # Gerador com fechamento (close) def contador(): for i in range(10): yield i print("\nFechando gerador:") c = contador() print(next(c)) c.close() try: print(next(c)) except StopIteration: print("Gerador fechado!") |
Geradores com yield são poderosos e eficientes. Eles permitem pausar e retomar a execução livremente.
Expressões geradoras: generator expressions
Generator expressions são como list comprehensions, mas com parênteses.
Elas produzem valores sob demanda, não uma lista inteira.
A sintaxe é (expressão for item in iterável).
Quando usar generator expressions? Em transformações de dados grandes.
Também quando você só precisa iterar uma vez sobre os valores.
A voz passiva é aplicada: “os elementos são produzidos um por um”.
Exemplo de generator expressions:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# Generator expression básica numeros = range(10) quadrados_gen = (x ** 2 for x in numeros) quadrados_lista = [x ** 2 for x in numeros] print("=== Generator Expressions ===") print(f"Generator: {quadrados_gen}") print(f"Lista: {quadrados_lista}") print(f"Tamanho do generator: {quadrados_gen.__sizeof__()} bytes") print(f"Tamanho da lista: {quadrados_lista.__sizeof__()} bytes") # Economia de memória com generator import sys lista_grande = [x for x in range(1000000)] gen_grande = (x for x in range(1000000)) print(f"\nMemória lista: {sys.getsizeof(lista_grande)} bytes") print(f"Memória generator: {sys.getsizeof(gen_grande)} bytes") # Generator com condicional pares_gen = (x for x in range(20) if x % 2 == 0) print(f"Pares (generator): {list(pares_gen)}") # Pipeline com generators (encadeamento) def gerar_numeros(n): for i in range(n): yield i quadrados = (x ** 2 for x in gerar_numeros(10)) pares_quadrados = (x for x in quadrados if x % 2 == 0) print(f"Pipeline: {list(pares_quadrados)}") # Generator com múltiplos for coordenadas_gen = ((x, y) for x in range(3) for y in range(3)) print(f"Coordenadas: {list(coordenadas_gen)}") # Generator para soma sem criar lista soma = sum(x ** 2 for x in range(1000000)) print(f"\nSoma de quadrados (sem lista): {soma}") # Generator para any/all tem_par = any(x % 2 == 0 for x in range(1000000)) todos_pares = all(x % 2 == 0 for x in range(10)) print(f"Tem par? {tem_par}") print(f"Todos pares? {todos_pares}") # Comparação de performance import timeit # Com lista comprehension def usando_lista(): return sum([x ** 2 for x in range(10000)]) # Com generator expression def usando_generator(): return sum(x ** 2 for x in range(10000)) tempo_lista = timeit.timeit(usando_lista, number=1000) tempo_gen = timeit.timeit(usando_generator, number=1000) print(f"\nList comprehension: {tempo_lista:.4f}s") print(f"Generator expression: {tempo_gen:.4f}s") |
Generator expressions economizam memória drasticamente. Elas são ideais para processamento de grandes volumes de dados.
Comparando geradores com listas e aplicações práticas
Geradores são melhores para grandes volumes de dados. Listas são melhores quando você precisa acessar múltiplas vezes. Quando usar cada um? Listas para dados pequenos e reuso. Geradores para streams grandes e iteração única. A voz passiva é aplicada: “os dados são processados em fluxo contínuo”. Exemplo prático e comparação detalhada:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# Comparação de consumo de memória import tracemalloc def medir_memoria(func): tracemalloc.start() resultado = func() atual, pico = tracemalloc.get_traced_memory() tracemalloc.stop() return resultado, pico / 1024 # KB def gerar_com_lista(): return [x ** 2 for x in range(1000000)] def gerar_com_generator(): return (x ** 2 for x in range(1000000)) print("=== Comparação de Memória ===") _, memoria_lista = medir_memoria(gerar_com_lista) _, memoria_gen = medir_memoria(gerar_com_generator) print(f"Lista: {memoria_lista:.0f} KB") print(f"Generator: {memoria_gen:.0f} KB") # Processamento de arquivo gigante (simulado) def processar_logs_com_generator(caminho): """Processa logs linha a linha sem carregar tudo.""" with open(caminho, 'r') as f: for linha in f: if "ERRO" in linha: yield linha.strip() print("\n=== Processamento de Logs ===") # Criando arquivo de log simulado with open("logs.txt", "w") as f: for i in range(10000): tipo = "ERRO" if i % 100 == 0 else "INFO" f.write(f"{tipo}: Mensagem {i}\n") erros = processar_logs_com_generator("logs.txt") contagem = 0 for erro in erros: contagem += 1 if contagem <= 5: print(f" {erro}") print(f"Total de erros encontrados: {contagem}") # Gerador para paginação de dados def paginar(dados, tamanho_pagina): """Divide dados em páginas usando gerador.""" for i in range(0, len(dados), tamanho_pagina): yield dados[i:i + tamanho_pagina] dados = list(range(100)) print("\n=== Paginação ===") for pagina in paginar(dados, 20): print(f"Página: {pagina[:5]}... (total {len(pagina)})") # Gerador para streaming de dados def streaming_dados(): """Simula streaming de dados de um sensor.""" import random import time while True: yield {"timestamp": time.time(), "valor": random.random()} time.sleep(0.1) print("\n=== Streaming (primeiros 5 valores) ===") stream = streaming_dados() for i in range(5): dado = next(stream) print(f" {dado['timestamp']:.2f}: {dado['valor']:.4f}") # Pipeline de processamento com geradores def ler_dados(): for i in range(1000): yield i def filtrar_pares(dados): for valor in dados: if valor % 2 == 0: yield valor def multiplicar_por_dois(dados): for valor in dados: yield valor * 2 def somar(dados): total = 0 for valor in dados: total += valor yield total pipeline = somar(multiplicar_por_dois(filtrar_pares(ler_dados()))) print(f"\nPipeline (primeiros 10 resultados): {list(pipeline)[:10]}") |
Geradores são fundamentais para programação eficiente. A fórmula da economia de memória com geradores: \(M_{\text{gerador}} \approx O(1) \quad \text{vs} \quad M_{\text{lista}} \approx O(n)\) Use geradores para grandes volumes de dados. Use listas quando precisar de acesso aleatório múltiplo. Geradores com yield são ideais para sequências complexas. Generator expressions são perfeitas para transformações simples. Seu código será mais eficiente e escalável com geradores.