Domina el protocolo de iteración y los generadores para procesar flujos de datos de forma perezosa, con memoria constante.
Abrir esta lección en KodokonEl for de Python no es más que un protocolo: iter(obj) pide un iterador, después se llama a next() hasta la excepción StopIteration. Distingue bien el iterable (la lista, capaz de entregar un iterador nuevo en cada pasada) del iterador (el cursor, que solo se puede recorrer una vez). Entender este protocolo es entender por qué algunos objetos "se consumen".
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
# next(iterator) would raise StopIterationUna función que contiene yield no se ejecuta al llamarla: devuelve un generador, un iterador cuyo cuerpo se ejecuta bajo demanda. Cada next() avanza hasta el siguiente yield, donde la ejecución queda suspendida junto con todo su estado local. La consecuencia directa: la memoria se mantiene constante sea cual sea el tamaño del flujo - produces los valores uno a uno en lugar de materializar una lista completa.
def squares(limit):
n = 0
while n < limit:
yield n * n
n += 1
gen = squares(4)
print(next(gen)) # 0
print(list(gen)) # [1, 4, 9]El beneficio principal en producción: los pipelines. Cada etapa consume el flujo de la anterior y produce el suyo, de forma perezosa; nada se ejecuta antes del consumo final (list(), sum(), un for). Un compromiso a tener en cuenta: una lista se puede recorrer varias veces y cortar en rebanadas, un generador no; en cambio, un generador procesa un archivo de 10 GB sin saturar la RAM. Las expresiones generadoras - (f(x) for x in xs if cond) - cubren las etapas sencillas sin definir una función.
lines = [" 12 ", "x", " 7", "", "30"]
def clean(rows):
for row in rows:
row = row.strip()
if row.isdigit():
yield int(row)
def keep_from(values, floor):
return (v for v in values if v >= floor)
pipeline = keep_from(clean(lines), 10)
print(list(pipeline)) # [12, 30]