Imagina procesar un archivo de 10 millones de líneas. Cargarlo todo en una lista consumiría gigas de memoria. Un generador produce cada valor justo cuando se necesita: memoria constante, trabajo diferido.
Iteradores: el protocolo detrás del for
Cuando escribes for x in lista, Python le pide a la lista un iterador y le va pidiendo valores con next() hasta que recibe la señal de fin (StopIteration):
numeros = iter([1, 2, 3]) # obtener un iterador manualmente
print(next(numeros)) # 1
print(next(numeros)) # 2
print(next(numeros)) # 3
next(numeros) # StopIteration → agotado
Un iterador es de un solo uso: agotado, no se reinicia.
Generadores: funciones con yield
Un generador es una función que en vez de return usa yield: produce valores uno a la vez y congela su estado entre llamada y llamada:
def contar_hasta(n):
i = 1
while i <= n:
yield i # entrega el valor y PAUSA aquí
i += 1 # reanuda aquí en el próximo next()
gen = contar_hasta(3)
print(next(gen)) # 1
print(next(gen)) # 2
for numero in contar_hasta(3): # el for consume el generador
print(numero)
La diferencia clave con una lista: los valores no existen todos a la vez. Se fabrican bajo demanda.
Compara el consumo
def primeros_millones_lista():
return [x for x in range(10_000_000)] # ~400 MB en memoria
def primeros_millones_gen():
for x in range(10_000_000):
yield x # bytes en memoria
range() mismo es perezoso — por eso puedes hacer for i in range(10**9) sin morir.
Expresiones generadoras
Como una list comprehension pero con paréntesis:
cuadrados_lista = [x ** 2 for x in range(5)] # lista: todo ya calculado
cuadrados_gen = (x ** 2 for x in range(5)) # generador: bajo demanda
print(sum(x ** 2 for x in range(1_000_000))) # ¡sin crear lista intermedia!
sum(), max(), any(), all() aceptan generadores directamente.
yield en cadena: pipelines
Los generadores se componen como tuberías:
def leer_lineas(texto):
for linea in texto.split("\n"):
yield linea
def solo_comentarios(lineas):
for linea in lineas:
if linea.strip().startswith("#"):
yield linea
codigo = "# config\nx = 1\n# fin"
for comentario in solo_comentarios(leer_lineas(codigo)):
print(comentario) # # config / # fin
Cada etapa produce de a un elemento: el pipeline nunca carga todo en memoria.
Resumen
forconsume iteradores connext()hastaStopIteration- Un generador (
yield) produce valores perezosamente recordando su estado - Memoria constante vs listas que cargan todo
(x for x in ...)es la expresión generadora — ideal consum/max/any- Generadores de un solo uso; si necesitas recorrer dos veces, crea uno nuevo