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.