DMarlon Studio

Lección 6 · 6 de 20

Generadores y yield · Python

Procese un registro de servidor línea a línea con generadores de Python: yield, next(), cadenas de generadores y expresiones generadoras.

Python · Generadores y yield en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una función con `yield` es un generador: al llamarla no se ejecuta, sino que devuelve un objeto que va entregando valores de uno en uno. read_lines(LOG) no crea una lista con todas las líneas; da una, se detiene, y sigue desde ahí cuando se le pide la siguiente. Con un registro de millones de líneas, la memoria apenas se nota.

errors(lines) consume otro generador: recorre las líneas, separa sus campos con split() y solo entrega las que tienen un código 400 o mayor. Encadenados, errors(read_lines(LOG)) forman una tubería en la que cada línea pasa por todas las fases antes de leer la siguiente.

Al crear pipeline no se ha ejecutado nada todavía (su tipo es generator). next(pipeline) lo hace avanzar hasta el primer yield y devuelve ese valor; el for siguiente continúa justo donde se quedó, por eso no repite el primer error. Un generador solo se recorre una vez.

Con paréntesis en lugar de corchetes se obtiene una expresión generadora: sum(1 for line in ... if ...) cuenta los errores 500 sin construir ninguna lista intermedia.

invoice_numbers tiene un while True, pero no se cuelga: solo calcula el número que se le pide. La comprensión final toma tres con next(numbers); el siguiente sería INV-2043.

Código · main.py

LOG = """2026-03-01 10:02 GET /shop 2002026-03-01 10:03 GET /cart 2002026-03-01 10:05 POST /pay 5002026-03-01 10:06 GET /shop 4042026-03-01 10:09 POST /pay 2002026-03-01 10:12 GET /admin 500"""def read_lines(text):                 # yields one line at a time    for line in text.splitlines():        yield linedef errors(lines):                    # consumes another generator    for line in lines:        date, time, method, path, status = line.split()        if int(status) >= 400:            yield f"{time} {path} -> {status}"pipeline = errors(read_lines(LOG))    # nothing has run yetprint(type(pipeline).__name__)print("First:", next(pipeline))       # runs until the first yieldfor item in pipeline:                 # continues where it stopped    print("Next: ", item)# Generator expression: like a comprehension, with ()total_500 = sum(1 for line in read_lines(LOG) if line.endswith("500"))print("Server errors:", total_500)def invoice_numbers(start):    while True:                       # infinite, but lazy        yield f"INV-{start}"        start += 1numbers = invoice_numbers(2040)print([next(numbers) for _ in range(3)])

Salida · python3 main.py

generator
First: 10:05 /pay -> 500
Next:  10:06 /shop -> 404
Next:  10:12 /admin -> 500
Server errors: 2
['INV-2040', 'INV-2041', 'INV-2042']

Para recordar

  • yield convierte una función en generador.
  • Un generador calcula cada valor cuando se le pide (es perezoso).
  • next() avanza hasta el siguiente yield; el for lo hace solo.
  • Un generador se agota: para recorrerlo otra vez, créelo de nuevo.
  • (expr for x in datos) es una expresión generadora.

Ejercicio

Añada un generador paths(lines) que entregue solo la ruta (/shop, /pay…) de cada línea y úselo para contar cuántas veces aparece /pay.