CodeGym /Cursos /Python SELF PT /Generadores

Generadores

Python SELF PT
Nível 13 , Lição 4
Disponível

3.1 Conhecendo os generadores

Generadores são funções que retornam um objeto iterador. Esses iteradores geram valores conforme solicitados, o que permite lidar com grandes conjuntos de dados sem carregá-los completamente na memória.

Existem várias maneiras de criar generadores, e abaixo vamos discutir as mais populares.

Generadores baseados em funções

Generadores são criados usando a palavra-chave yield dentro de uma função. Quando a função com yield é chamada, ela retorna um objeto generador, mas não executa o código dentro da função imediatamente. Em vez disso, a execução é pausada na expressão yield e é retomada a cada chamada do método __next__() do objeto generador.


def count_up_to(max):
    count = 1
    while count <= max:
        yield count
        count += 1
        
counter = count_up_to(5)
print(next(counter))  # Saída: 1
print(next(counter))  # Saída: 2
print(next(counter))  # Saída: 3
print(next(counter))  # Saída: 4
print(next(counter))  # Saída: 5

Se uma função tem a instrução yield, o Python cria um objeto generador em vez de executar a função de maneira tradicional, que gerencia o estado da execução da função.

Expressões de generadores

As expressões de generadores são semelhantes às List Comprehensions, mas são criadas usando parênteses em vez de colchetes. Elas também retornam um objeto generador.


squares = (x ** 2 for x in range(10))

print(next(squares))  # Saída: 0
print(next(squares))  # Saída: 1
print(next(squares))  # Saída: 4

Qual das maneiras você prefere?

3.2 Vantagens dos generadores

Uso eficiente de memória

Generadores calculam valores sob demanda, o que permite lidar com grandes dados sem carregá-los completamente na memória. Isso torna os generadores uma escolha ideal para trabalhar com grandes conjuntos de dados ou fluxos de dados.


def large_range(n):
    for i in range(n):
        yield i
        
for value in large_range(1000000):
    # Processamos os valores um por um
    print(value)

Cálculos preguiçosos

Generadores realizam cálculos preguiçosos, o que significa que eles calculam valores somente quando necessário. Isso evita cálculos desnecessários e melhora o desempenho.


def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b
        
fib = fibonacci()
for _ in range(10):
    print(next(fib))

Facilidade de sintaxe

Generadores proporcionam uma sintaxe fácil para criar iteradores, o que simplifica a escrita e leitura do código.

3.3 Uso de generadores

Exemplos de uso de generadores na biblioteca padrão

Muitas funções na biblioteca padrão do Python usam generadores. Por exemplo, a função range() retorna um objeto generador, que gera uma sequência de números.


for i in range(10):
    print(i)

Sim, o mundo nunca mais será o mesmo.

Criando sequências infinitas

Generadores permitem criar sequências infinitas, que podem ser úteis em diversos cenários, como a geração de fluxos infinitos de dados.


def natural_numbers():
    n = 1
    while True:
        yield n
        n += 1
        
naturals = natural_numbers()
for _ in range(10):
    print(next(naturals))

Uso de send() e close()

Objetos generadores suportam os métodos send() e close(), que permitem enviar valores de volta para o generador e encerrar sua execução.


def echo():
    while True:
        received = yield
        print(received)
        
e = echo()
next(e)  # Inicia o generador
e.send("Hello, world!")  # Saída: Hello, world!
e.close()

3.4 Generadores na prática

Generadores e exceções

Generadores podem lidar com exceções, o que os torna uma ferramenta poderosa para a escrita de código mais robusto.


def controlled_execution():
    try:
        yield "Start"
        yield "Working"
    except GeneratorExit:
        print("Generator closed")
        
gen = controlled_execution()
print(next(gen))  # Saída: Start
print(next(gen))  # Saída: Working
gen.close()  # Saída: Generator closed

Trabalhar com exceções será abordado nas próximas palestras, mas acho útil saber que generadores lidam bem com elas.

Generadores aninhados

Generadores podem ser aninhados, o que permite criar estruturas iterativas complexas.


def generator1():
    yield from range(3)
    yield from "ABC"
        
for value in generator1():
    print(value)

# Saída
0
1
2
A
B
C

Explicação:

yield from: Esta construção é usada para delegar parte das operações a outro generador, o que simplifica o código e melhora a legibilidade.

Generadores e desempenho

O uso de generadores pode melhorar significativamente o desempenho dos programas ao reduzir o uso de memória e executar iterações de forma mais eficiente.

Exemplo de comparação entre listas e generadores


import time
import sys

def memory_usage(obj):
    return sys.getsizeof(obj)

n = 10_000_000

# Usando lista
start_time = time.time()
list_comp = [x ** 2 for x in range(n)]
list_time = time.time() - start_time
list_memory = memory_usage(list_comp)

# Usando generador
start_time = time.time()
gen_comp = (x ** 2 for x in range(n))
gen_result = sum(gen_comp)  # Calculamos a soma para comparar os resultados
gen_time = time.time() - start_time
gen_memory = memory_usage(gen_comp)

print(f"Lista:")
print(f"  Tempo: {list_time:.2f} seg")
print(f"  Memória: {list_memory:,} bytes")

print(f"\nGenerador:")
print(f"  Tempo: {gen_time:.2f} seg")
print(f"  Memória: {gen_memory:,} bytes")

Lista:
  Tempo: 0.62 seg
  Memória: 89,095,160 bytes

Generador:
  Tempo: 1.13 seg
  Memória: 200 bytes
1
Pesquisa/teste
Funções do sistema, nível 13, lição 4
Indisponível
Funções do sistema
Funções do sistema
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION