Generadores para Principiantes

Analogía: La Máquina de Chicles 🍬

Imagina una máquina de chicles:

  • No te da todos los chicles a la vez

  • Cada vez que giras la manivela te da un chicle

  • Puedes parar cuando quieras

  • La máquina recuerda dónde quedó para la próxima vez

Un generador es como esa máquina: te da valores uno por uno cuando los pides.

Los generadores son funciones especiales en Python que permiten producir una secuencia de valores de forma pausada y eficiente, en lugar de calcular todos los valores a la vez y guardarlos en memoria.

Diferencia clave: Función normal vs Generador

Función normal (usa return)

python
def numeros_cuadrados(n):
    resultado = []
    for i in range(n):
        resultado.append(i ** 2)
    return resultado

# Todos los valores se calculan y almacenan en memoria
lista = numeros_cuadrados(5)
print(lista)  # [0, 1, 4, 9, 16]

Generador (usa yield)

python
def numeros_cuadrados_gen(n):
    for i in range(n):
        yield i ** 2  # Produce un valor y se pausa

# Los valores se generan bajo demanda
generador = numeros_cuadrados_gen(5)
print(generador)  # <generator object...>

Cómo usar generadores

1. Iteración directa

python
def contador_hasta(n):
    i = 0
    while i < n:
        yield i
        i += 1

for numero in contador_hasta(3):
    print(numero)
# Output: 0, 1, 2

2. Usando next()

python
def simple_generator():
    yield "primero"
    yield "segundo"
    yield "tercero"

gen = simple_generator()
print(next(gen))  # "primero"
print(next(gen))  # "segundo"
print(next(gen))  # "tercero"
# print(next(gen))  # Error: StopIteration

Expresiones de generador (más compactas)

Similar a las listas por comprensión, pero con paréntesis:

python
# Lista por comprensión (almacena todo en memoria)
lista = [x**2 for x in range(5)]  # [0, 1, 4, 9, 16]

# Expresión de generador (genera bajo demanda)
generador = (x**2 for x in range(5))
print(list(generador))  # [0, 1, 4, 9, 16]

Ejemplos prácticos para principiantes

Ejemplo 1: Generador infinito

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

contador = contador_infinito()
print(next(contador))  # 0
print(next(contador))  # 1
print(next(contador))  # 2
# Y así continúa indefinidamente...

Ejemplo 2: Procesar archivo grande

python
def leer_lineas_archivo(nombre_archivo):
    with open(nombre_archivo, 'r') as archivo:
        for linea in archivo:
            yield linea.strip()

# Solo carga una línea a la vez en memoria
for linea in leer_lineas_archivo("datos.txt"):
    print(linea)

Ejemplo 3: Fibonacci con generador

python
def fibonacci(limite):
    a, b = 0, 1
    count = 0
    while count < limite:
        yield a
        a, b = b, a + b
        count += 1

# Genera solo los números que necesitas
for num in fibonacci(5):
    print(num)  # 0, 1, 1, 2, 3

Ventajas de los generadores

  1. Eficiencia en memoria: No almacenan todos los valores a la vez

  2. Lazy evaluation: Los valores se calculan solo cuando se necesitan

  3. Pueden representar secuencias infinitas

  4. Mantenimiento de estado: Recuerdan dónde se quedaron

Cuándo usar generadores

  • ✅ SÍ usar cuando:

    • Trabajas con grandes volúmenes de datos

    • No necesitas acceso aleatorio a los elementos

    • Quieres ahorrar memoria

    • Trabajas con flujos de datos o secuencias

  • ❌ NO usar cuando:

    • Necesitas acceder a los elementos múltiples veces

    • Requieres acceso aleático a los elementos

    • Necesitas conocer la longitud de antemano

Resumen para principiantes

CaracterísticaFunción normalGenerador
Retornoreturnyield
MemoriaAlmacena todoGenera bajo demanda
EstadoSe reiniciaMantiene estado
UsoMúltiples accesosFlujo único

Los generadores son una herramienta poderosa que te ayudará a escribir código más eficiente y elegante en Python. ¡Empieza con ejemplos simples y verás lo útiles que son!


¿Qué es un Generador?

Es una función que produce una secuencia de valores pero no los almacena todos en memoria. En lugar de usar return, usa yield.


Ejemplo 1: Generador Básico - Contador 🎯

python
def contador_generador(limite):
    numero = 1
    while numero <= limite:
        yield numero  # Pausa aquí y recuerda dónde quedó
        numero += 1

# Usar el generador
mi_contador = contador_generador(5)

print("=== GIRANDO LA MÁQUINA ===")
print(next(mi_contador))  # Primer "giro" - Output: 1
print(next(mi_contador))  # Segundo "giro" - Output: 2
print(next(mi_contador))  # Tercer "giro" - Output: 3

Output:

text
=== GIRANDO LA MÁQUINA ===
1
2
3

Usando un bucle for (más común):

python
def contador_generador(limite):
    numero = 1
    while numero <= limite:
        yield numero
        numero += 1

print("=== CON BUCLE FOR ===")
for numero in contador_generador(5):
    print(f"Chicle número: {numero}")

Output:

text
=== CON BUCLE FOR ===
Chicle número: 1
Chicle número: 2
Chicle número: 3
Chicle número: 4
Chicle número: 5

Ejemplo 2: Generador de Números Pares 🔢

python
def numeros_pares(limite):
    numero = 0
    while numero <= limite:
        if numero % 2 == 0:
            yield numero  # Pausa y devuelve este número par
        numero += 1

print("=== NÚMEROS PARES ===")
for par in numeros_pares(10):
    print(f"Número par: {par}")

Output:

text
=== NÚMEROS PARES ===
Número par: 0
Número par: 2
Número par: 4
Número par: 6
Número par: 8
Número par: 10

Comparación con lista normal:

python
# CON LISTA (ocupa más memoria)
def pares_lista(limite):
    resultado = []
    for i in range(limite + 1):
        if i % 2 == 0:
            resultado.append(i)
    return resultado

# CON GENERADOR (más eficiente en memoria)
def pares_generador(limite):
    for i in range(limite + 1):
        if i % 2 == 0:
            yield i

print("Lista completa:", pares_lista(10))  # [0, 2, 4, 6, 8, 10]

print("Generador:")
for num in pares_generador(10):
    print(num, end=" ")  # 0 2 4 6 8 10

Ejemplo 3: Generador de Mensajes Personalizados 🎁

python
def mensaje_generador(nombres):
    for nombre in nombres:
        mensaje = f"🎁 ¡Hola {nombre}! Tienes un regalo."
        yield mensaje  # Pausa y entrega un mensaje

# Lista de nombres
amigos = ["Ana", "Carlos", "María", "Pedro"]

print("=== ENTREGANDO MENSAJES ===")
mensajero = mensaje_generador(amigos)

# Entregamos los mensajes uno por uno
print(next(mensajero))  # 🎁 ¡Hola Ana! Tienes un regalo.
print(next(mensajero))  # 🎁 ¡Hola Carlos! Tienes un regalo.
print(next(mensajero))  # 🎁 ¡Hola María! Tienes un regalo.

# Podríamos seguir con next() o usar un bucle

Output:

text
=== ENTREGANDO MENSAJES ===
🎁 ¡Hola Ana! Tienes un regalo.
🎁 ¡Hola Carlos! Tienes un regalo.
🎁 ¡Hola María! Tienes un regalo.

Ejemplo 4: Generador con Condición - Solo Números Grandes 🏗️

python
def numeros_grandes(numeros, minimo):
    for numero in numeros:
        if numero >= minimo:
            yield f"🚀 Número grande: {numero}"
        else:
            yield f"🐜 Número pequeño: {numero}"

lista_numeros = [5, 15, 3, 25, 8, 30]

print("=== FILTRANDO NÚMEROS ===")
for mensaje in numeros_grandes(lista_numeros, 10):
    print(mensaje)

Output:

text
=== FILTRANDO NÚMEROS ===
🐜 Número pequeño: 5
🚀 Número grande: 15
🐜 Número pequeño: 3
🚀 Número grande: 25
🐜 Número pequeño: 8
🚀 Número grande: 30

Ventajas de los Generadores

1. Ahorro de Memoria 💾

python
# MAL: Crea una lista gigante en memoria
def numeros_gigante_mal(limite):
    return list(range(limite))  # ¡Ocupa mucha memoria!

# BIEN: Generador que produce números uno por uno
def numeros_gigante_bien(limite):
    for i in range(limite):
        yield i  # Solo ocupa memoria para un número a la vez

2. Pueden ser infinitos ♾️

python
def contador_infinito():
    numero = 1
    while True:
        yield numero
        numero += 1

# ¡Cuidado! Esto no crea un bucle infinito de inmediato
infinito = contador_infinito()

print(next(infinito))  # 1
print(next(infinito))  # 2
print(next(infinito))  # 3
# Podemos seguir para siempre...

Resumen Visual

text
GENERADOR NORMAL: [1, 2, 3, 4, 5] → Te da TODOS los números de una vez

GENERADOR PYTHON: 1 → 2 → 3 → 4 → 5 → Te da UN número cada vez que pides
                    ↑    ↑    ↑    ↑    ↑
                 yield yield yield yield yield

Diferencias Clave:

Función NormalGenerador
Usa returnUsa yield
Devuelve todo de una vezDevuelve un valor a la vez
Olvida el estadoRecuerda dónde quedó
Ocupa más memoriaMuy eficiente en memoria

Sintaxis Express: Generator Expressions

python
# Similar a list comprehension pero con paréntesis
lista = [x * 2 for x in range(5)]        # [0, 2, 4, 6, 8]
generador = (x * 2 for x in range(5))    # Generator object

print("Lista:", lista)
print("Generador:", list(generador))     # Convertimos a lista para ver

Puntos Clave para Principiantes:

  1. yield pausa la función y recuerda el estado

  2. next() continúa desde donde se pausó

  3. Son perezosos (lazy) - solo calculan cuando se necesita

  4. Ideales para secuencias grandes o infinitas


Comentarios

Entradas populares de este blog

¿Qué es un Closure?

Calculadora de edad

Funciones en Python: con y sin paréntesis