Volver a Python Intermedio

Generadores e Iteradores

Los generadores te permiten producir valores de forma perezosa (lazy), uno a la vez, ahorrando memoria para conjuntos de datos grandes.

Protocolo de Iteradores

Cualquier objeto con __iter__() y __next__() es un iterador:

class ContarArriba: def __init__(self, limite): self.limite = limite self.actual = 0 def __iter__(self): return self def __next__(self): if self.actual >= self.limite: raise StopIteration valor = self.actual self.actual += 1 return valor for n in ContarArriba(5): print(n) # 0 1 2 3 4

Funciones Generadoras

def contar_arriba(limite): actual = 0 while actual < limite: yield actual # Se pausa aquí, continúa en el próximo next() actual += 1 gen = contar_arriba(5) print(next(gen)) # 0 print(next(gen)) # 1 for n in contar_arriba(5): print(n)

Generadores Infinitos

def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b fib = fibonacci() primeros_10 = [next(fib) for _ in range(10)] print(primeros_10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Expresiones Generadoras

# Comprensión de lista (ansiosa, almacena todo en memoria) cuadrados_lista = [x**2 for x in range(1_000_000)] # Expresión generadora (perezosa, genera uno a la vez) cuadrados_gen = (x**2 for x in range(1_000_000)) print(sum(cuadrados_gen)) # Calculado de forma perezosa

yield from

def aplanar(anidado): for item in anidado: if isinstance(item, list): yield from aplanar(item) # Delegar al sub-generador else: yield item datos = [1, [2, [3, 4], 5], [6, 7]] print(list(aplanar(datos))) # [1, 2, 3, 4, 5, 6, 7]

Enviar Valores a Generadores

def acumulador(): total = 0 while True: valor = yield total # Ceder total actual, recibir nuevo valor if valor is None: break total += valor acc = acumulador() next(acc) # Inicializar el generador acc.send(10) # total = 10 acc.send(20) # total = 30 resultado = acc.send(5) print(resultado) # 35

Práctico: Streaming de Líneas de Archivo

def leer_archivo_grande(ruta): """Lee un archivo enorme línea por línea sin cargarlo todo.""" with open(ruta, "r") as f: for linea in f: yield linea.strip() # Procesar millones de líneas con memoria constante for linea in leer_archivo_grande("datos_grandes.csv"): procesar(linea)

Ventaja de memoria: Un generador para 1,000,000 enteros usa ~120 bytes vs ~8 MB para una lista.