CodeGym /Cursos /Python SELF ES /Generadores

Generadores

Python SELF ES
Nivel 13 , Lección 4
Disponible

4.1 Introducción a los generadores

Generadores son funciones que devuelven un objeto iterador. Estos iteradores generan valores según se solicitan, lo que permite manejar conjuntos de datos potencialmente grandes sin cargarlos completamente en la memoria.

Hay varias formas de crear generadores, a continuación se discutirán las más populares.

Generadores basados en funciones

Los generadores se crean usando la palabra clave yield dentro de una función. Cuando se llama una función con yield, devuelve un objeto generador, pero no ejecuta el código dentro de la función inmediatamente. En su lugar, la ejecución se suspende en la expresión yield y se reanuda con cada llamada al método __next__() del objeto generador.


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

Si hay una declaración yield en la función, Python en lugar de la ejecución tradicional de la función crea un objeto generador que gestiona el estado de ejecución de la función.

Expresiones generadoras

Las expresiones generadoras son similares a las List Comprehension, pero se crean usando paréntesis en lugar de corchetes. También devuelven un objeto generador.


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

print(next(squares))  # Salida: 0
print(next(squares))  # Salida: 1
print(next(squares))  # Salida: 4

¿Cuál de las formas te gusta más?

3.2 Ventajas de los generadores

Uso eficiente de memoria

Los generadores calculan valores sobre la marcha, lo que permite manejar grandes datos sin cargarlos completamente en la memoria. Esto hace que los generadores sean una opción ideal para trabajar con grandes conjuntos de datos o flujos de datos.


def large_range(n):
    for i in range(n):
        yield i
        
for value in large_range(1000000):
    # Procesamos los valores uno por uno
    print(value)

Cálculo perezoso

Los generadores realizan cálculos perezosos, es decir, calculan valores solo cuando es necesario. Esto permite evitar cálculos innecesarios y mejora el rendimiento.


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

Conveniencia sintáctica

Los generadores proporcionan una sintaxis conveniente para crear iteradores, lo que simplifica la escritura y lectura del código.

3.3 Uso de generadores

Ejemplos de uso de generadores en la biblioteca estándar

Muchas funciones en la biblioteca estándar de Python utilizan generadores. Por ejemplo, la función range() devuelve un objeto generador, que genera una secuencia de números.


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

Sí, el mundo ya nunca será el mismo.

Creación de secuencias infinitas

Los generadores permiten crear secuencias infinitas, que pueden ser útiles en varios escenarios, como la generación de flujos de datos infinitos.


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

Uso de send() y close()

Los objetos generadores soportan los métodos send() y close(), que permiten enviar valores de vuelta al generador y terminar su ejecución.


def echo():
    while True:
        received = yield
        print(received)
        
e = echo()
next(e)  # Iniciamos el generador
e.send("Hello, world!")  # Salida: Hello, world!
e.close()

3.4 Generadores en la práctica

Generadores y excepciones

Los generadores pueden manejar excepciones, lo que los convierte en una herramienta poderosa para escribir código más resistente.


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

Veremos el manejo de excepciones en las próximas lecciones, pero creo que será útil que sepas que los generadores funcionan bien con ellas.

Generadores anidados

Los generadores pueden ser anidados, lo que permite crear estructuras iterativas complejas.


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

# Salida
0
1
2
A
B
C

Explicación:

yield from: Esta construcción se utiliza para delegar parte de las operaciones a otro generador, lo que permite simplificar el código y mejorar la legibilidad.

Generadores y rendimiento

El uso de generadores puede mejorar significativamente el rendimiento de los programas al reducir el uso de memoria y efectuar iteraciones de manera más eficiente.

Ejemplo de comparación entre listas y generadores


import time
import sys

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

n = 10_000_000

# Uso de 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)

# Uso de generador
start_time = time.time()
gen_comp = (x ** 2 for x in range(n))
gen_result = sum(gen_comp)  # Calculamos la suma para comparar resultados
gen_time = time.time() - start_time
gen_memory = memory_usage(gen_comp)

print(f"Lista:")
print(f"  Tiempo: {list_time:.2f} seg")
print(f"  Memoria: {list_memory:,} bytes")

print(f"\nGenerador:")
print(f"  Tiempo: {gen_time:.2f} seg")
print(f"  Memoria: {gen_memory:,} bytes")

Lista:
  Tiempo: 0.62 seg
  Memoria: 89,095,160 bytes

Generador:
  Tiempo: 1.13 seg
  Memoria: 200 bytes
1
Cuestionario/control
Funciones del sistema, nivel 13, lección 4
No disponible
Funciones del sistema
Funciones del sistema
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION