Generadores en Python: yield y optimización

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Los generadores en Python son un mecanismo potente para trabajar eficientemente con secuencias de datos. Permiten crear objetos iterables que calculan valores bajo demanda, ahorrando significativamente memoria del sistema. La base de los generadores es la palabra clave yield, que se diferencia radicalmente del tradicional return.

Fundamentos de los generadores en Python

Los generadores son funciones especiales que devuelven iteradores en lugar de valores concretos. La principal diferencia radica en el uso de la palabra clave yield, que conserva el estado de la función entre llamadas.

Cada vez que se accede a una función generadora, la ejecución se reanuda desde el punto donde se detuvo la última instrucción yield. Esto hace que los generadores sean la solución ideal para procesar grandes flujos de datos sin necesidad de cargarlos completamente en memoria.

def generador_simple():
    yield 1
    yield 2
    yield 3

gen = generador_simple()
for valor in gen:
    print(valor)

Resultado de la ejecución:

1
2
3

Mecanismo de la palabra clave yield

La palabra clave yield funciona bajo el principio de "evaluación perezosa" (lazy evaluation):

  1. En la primera llamada a next(), la ejecución comienza desde el inicio de la función hasta el primer yield
  2. Después de devolver el valor, la función "congela" su estado
  3. La siguiente llamada a next() reanuda la ejecución desde donde se detuvo
  4. El proceso continúa hasta que la función finaliza
def contador():
    print("Inicio del generador")
    yield 1
    print("Después del primer yield")
    yield 2
    print("Después del segundo yield")

gen = contador()
next(gen)  # Imprime: Inicio del generador
next(gen)  # Imprime: Después del primer yield

Ventajas de los generadores frente a las listas

Usar generadores en lugar de listas ofrece ventajas significativas al trabajar con grandes volúmenes de datos:

Ahorro de memoria: Los generadores no almacenan todos los valores a la vez, sino que los crean según se necesitan.

# Lista - ocupa mucha memoria
lista_cuadrados = [x * x for x in range(1000000)]

# Generador - consumo mínimo de memoria
gen_cuadrados = (x * x for x in range(1000000))

Rendimiento: Los generadores comienzan a devolver valores de inmediato, sin esperar a crear la secuencia completa.

Creación de secuencias infinitas

Los generadores permiten crear secuencias infinitas sin riesgo de desbordar la memoria:

def contador_infinito(inicio=0):
    while True:
        yield inicio
        inicio += 1

contador = contador_infinito()
print(next(contador))  # 0
print(next(contador))  # 1
print(next(contador))  # 2

Métodos para trabajar con generadores

Los generadores admiten varios métodos especializados:

next() — obtiene el siguiente valor del generador
send(valor) — envía un valor al interior del generador
throw() — lanza una excepción dentro del generador
close() — detiene el generador de forma forzada

def generador_interactivo():
    valor = yield "Inicio"
    while True:
        valor = yield f"Recibido: {valor}"

gen = generador_interactivo()
print(next(gen))           # Inicio
print(gen.send(42))        # Recibido: 42
print(gen.send("Hola"))    # Recibido: Hola

Manejo de excepciones en generadores

Los generadores soportan un mecanismo de manejo de excepciones, incluyendo la excepción especial GeneratorExit:

def generador_ejemplo():
    try:
        yield 1
        yield 2
    except GeneratorExit:
        print("¡Generador cerrado!")

gen = generador_ejemplo()
print(next(gen))  # 1
gen.close()       # Imprime: ¡Generador cerrado!

Expresiones generadoras (Generator Expressions)

Además de las funciones generadoras, Python ofrece una sintaxis compacta llamada expresiones generadoras, ideales para transformaciones simples:

# Expresión generadora
pares = (x for x in range(20) if x % 2 == 0)
print(list(pares))  # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# Equivalente con función generadora
def generar_pares(limite):
    for x in range(limite):
        if x % 2 == 0:
            yield x

print(list(generar_pares(20)))  # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Conclusión

Los generadores en Python son una herramienta esencial para cualquier desarrollador que trabaje con datos. Su capacidad para optimizar el uso de memoria y mejorar el rendimiento los convierte en la opción preferida frente a las listas tradicionales, especialmente en aplicaciones que procesan grandes volúmenes de información o flujos de datos continuos.

Blogs

Book Recommendations