image

Acesse bootcamps ilimitados e +650 cursos

50
%OFF
Article image
Antonio Guedes
Antonio Guedes04/07/2025 00:18
Compartilhe
Savegnago - Lógica de ProgramaçãoRecomendados para vocêSavegnago - Lógica de Programação

GERADORES a forma mais Pythônica de lidar com Fluxo de Dados

    A Forma Mais Eficiente de LIdar Com Fluxo De Dados

    Os Geradores são a forma mais Pythônica de ter o melhor dos Iteradores e das Funções: A concisão de uma função e a efiência de memória de um iterador. Assim como, são a maneira muito mais simples e concisa de criar um iterador.

    Vamos pensar nos geradores como uma "função preguiçosa" (lazy function) ou uma "fábrica de iteradores"

    • É uma função que, em vez de return um único valor e terminar, usa a palavra-chave yield para produzir (gerar) uma sequência de valores.
    • Cada vez que yield é encontrado, a função "pausa" sua execução, "retorna" o valor gerado e mantém seue stado interno.
    • Quando o próximo valor é solicitado (por exemplo, em um loop for ou com next()), a função retoma a execução de onde parou, continuando até o próximo yield ou até o fim da função.
    • Quando a função geradora termina (não há mais yields ou há um return sem valor), ela levanta automaticamente uma SotpIteration, indicando o fim da sequência, exatamente como um iterador.

    Uma grande vantagem é que não é preciso escrever uma classe complexa com __iter__() e __next__() para criar um iterador; o Python faz todo o trabalho de gerenciar o estado para você.

    COMO USAR GERADORES?

    Usa como se usa qualquer iterador:

    1. Chame a função geradora: Isso não executa o código dentro dela imediatament, mas retorna um objeto gerador.
    2. Itere sobre o objeto gerador: Use um loop for ou chame next() nele para começar a obter os valores.ValueError

     

    Exemplo simples de um gerador

    def meu_gerador():
    yield 1
    yield 2
    yield 3
    

    1. Chamar a função retorna um objeto gerador (que é um iterador!)

    gen_obj = meu_gerador()
    print(f"  Tipo de objeto retornado: {type(gen_obj)}") # Tipo de objeto retornado: <class 'generator'>
    

    2. Iterar sobre ele

    print("Valores do gerador:")
    for valor in gen_obj:
    print(valor)
    
    # SAÍDA
    # Valores do gerador:
    # 1
    # 2
    # 3
    

    Se tentar iterar novamente, ele estará "esgotado"

    for valor in gen_obj:
    print(valor) # Não vai imprimir nada
    

    COMPARAR FIBONACCI COM ITERADOR, GERADOR E FUNÇÃO

    Vamos colocar os três lado a lado e medir a performance de memória e tempo para provar o ponto.

    import time
    from time import perf_counter
    from pympler.asizeof import asizeof
    
    LIMITE_FIBONACCI = 100000
    

    ITERADOR

    class FibonacciIterator:
    def __init__(self, limite):
      self.limite = limite
      self.a = 0 # Primeiro elemento da sequência
      self.b = 1 # Segundo elemento da sequência
      self.contador = 0 # Para contar quantos números já foram gerados
    
    def __iter__(self):
      # Retorna o próprio objeto, pois ele já é um iterador
      return self
    
    def __next__(self):
      # A lógica para gerar o próximo elemento da sequência
      if self.contador < self.limite:
        if self.contador == 0:
          self.contador += 1
          return self.a # Retorna 0 para o primeiro elemento
        elif self.contador == 1:
          self.contador +=1
          return self.b # Retorna 1 para o segundo elemento
        else:
          proximo_fib = self.a + self.b
          self.a = self.b
          self.b = proximo_fib
          self.contador += 1
          return proximo_fib
      else:
        # QUando o limite é atigido, levantamos o StopIteration
        raise StopIteration
     
    
    fibo_iterador = FibonacciIterator(LIMITE_FIBONACCI)
    
    print("\n### Teste 1: Classe Iterador ###")
    inicio_iter = perf_counter()
    for i, num in enumerate(fibo_iterador):
    if i == 0:
      print(f"  Tamanho do objeto iterador (primeira iteração): {asizeof(fibo_iterador)} bytes")
      print(f"  Tamanho de um número gerado (ex: {num}): {asizeof(num)} bytes")
    pass
    
    fim_iter = perf_counter()
    print(f"  Tempo de execução (apenas iteração): {fim_iter - inicio_iter:.6f} segundos")
    print(f"  Consumo de memória para a sequência: Baixo e constante (não armazena tudo).")
    print(f"  (O iterador gerou {i+1} números)")
    print(f"  Tipo fibo_iterador: {type(fibo_iterador)}")
    
    
    # SAÍDA
    ### Teste 1: Classe Iterador ###
    # Tamanho do objeto iterador (primeira iteração): 640 bytes
    # Tamanho de um número gerado (ex: 0): 32 bytes
    # Tempo de execução (apenas iteração): 0.165058 segundos
    # Consumo de memória para a sequência: Baixo e constante (não armazena tudo).
    # (O iterador gerou 100000 números)
    

    FUNÇÂO gera lista

    def fibonacci_lista(limite):
    a = 0
    b = 1
    contador = 0
    sequencia = []
    while contador < limite:
      sequencia.append(a)
      proximo_item = a + b
      a = b
      b = proximo_item
      contador += 1
    return sequencia
    
    fib_list = fibonacci_lista(LIMITE_FIBONACCI)
    
    print("\n### Teste 2: Função que Retorna Lista ###")
    
    inicio_lista = perf_counter()
    # Chamar a função que gera e retorna a lista completa
    fib_list = fibonacci_lista(LIMITE_FIBONACCI)
    fim_lista = perf_counter()
    
    print(f"  Tempo de execução (gerando e retornando lista): {fim_lista - inicio_lista:.6f} segundos")
    print(f"  Consumo de memória para a lista (pympler.asizeof): {asizeof(fib_list)} bytes")
    print(f"  (A lista contém {len(fib_list)} números)")
    
    # Para ver alguns números e provar que a lista existe
    print(f"  Primeiros 5 números na lista: {fib_list[:5]}")
    print(f"  Tipo fibo_list: {type(fib_list)}") # Tipo de fib_list: <class 'list'>
    
    # SAÍDA
    # ### Teste 2: Função que Retorna Lista ###
    #   Tempo de execução (gerando e retornando lista): 0.474742 segundos
    #   Consumo de memória para a lista (pympler.asizeof): 466408808 bytes
    #   (A lista contém 100000 números)
    #   Primeiros 5 números na lista: [0, 1, 1, 2, 3]
    

    FUNÇÂO GERADORA de Fibonacci (Nova!)

    def fibonacci_gerador(LIMITE_FIBONACCI):
    a, b = 0, 1
    contador = 0
    while contador < LIMITE_FIBONACCI:
      yield a # Pausa aqui, retorna 'a', e mantém o estado
      a, b = b, a + b
      contador += 1
    
    
    print("\n### Teste 3: Função Geradora ###")
    # Chamar a função geradora para obter o objeto gerador
    fib_gen = fibonacci_gerador(LIMITE_FIBONACCI)
    
    
    inicio_gen = perf_counter()
    # Iterar sobre o objeto gerador para obter os números
    for i, num in enumerate(fib_gen):
      if i == 0: # Medir o tamanho do objeto gerador uma vez no início
          print(f"  Tamanho do objeto gerador (primeira iteração): {asizeof(fib_gen)} bytes")
          print(f"  Tamanho de um número gerado (ex: {num}): {asizeof(num)} bytes")
      # A memória consumida permanece constante (além do próprio número sendo processado)
      pass # Apenas itera, não armazena para provar a eficiência de memória
    
    fim_gen = perf_counter()
    print(f"  Tempo de execução (apenas iteração): {fim_gen - inicio_gen:.6f} segundos")
    print(f"  Consumo de memória para a sequência: Baixo e constante (não armazena tudo).")
    print(f"  (O gerador gerou {i+1} números)")
    print(f"  Tipo de fib_gen: {type(fib_gen)}") # Tipo de fib_gen: <class 'generator'>
    
    # SAIDA
    #  Tamanho do objeto gerador (primeira iteração): 256 bytes
    #   Tamanho de um número gerado (ex: 0): 32 bytes
    #   Tempo de execução (apenas iteração): 0.129211 segundos
    #   Consumo de memória para a sequência: Baixo e constante (não armazena tudo).
    #   (O gerador gerou 100000 números)
    

    RESULTADOS

    MÉTODO       | TEMPO   | CONSUMO DE MEMÓRIA

    ITERADOR      | 0.167704 s | 32 bytes

    Função Retorna Lista | 0.478771 s | 466408808 bytes

    Função Geradora   | 0.129211 s | 32 bytes

    CONCLUSÃO

    Essa comparação visual e numérica prova o ponto:

    • Funções que geram listas são fáceis de usar quando você precisa de todos os dados de uma vez e o conjunto de dados é pequeno. A desvantagem é o alto consumo de memória para grandes volumes.
    • Iteradores (via classes) oferecem eficiência de memória, mas exigem mais código (__init____iter____next__).
    • Geradores (via funções yield) oferecem a mesma eficiência de memória dos iteradores, mas com uma sintaxe muito mais concisa e legível. Eles são a forma mais Pythônica de lidar com fluxos de dados "preguiçosos" e são a escolha ideal para a maioria dos casos onde você precisa de um iterador customizado.

    É isso aí!

    Fim da série Decoradores, Iteradores e Geradores espero que tenha gostado de ler esta série 😉

    Compartilhe
    Recomendados para você
    Deal Group - AI Centric .NET
    Randstad - Análise de Dados
    BairesDev - Machine Learning Training
    Comentários (1)
    Carlos Barbosa
    Carlos Barbosa - 04/07/2025 01:41

    Python é realmente formidável !! Ótimo post !! 🤓

    Recomendados para vocêSavegnago - Lógica de Programação