La palabra clave yield transforma una función normal en un generador. En lugar de calcular y almacenar todos los resultados antes de devolverlos, el generador produce un valor, pausa su ejecución y continúa desde el mismo punto cuando se solicita el siguiente elemento. Esta evaluación perezosa permite procesar secuencias grandes con un consumo de memoria mucho menor.
Para comprender bien este tema, conviene conocer los bucles for en Python y las comprensiones de listas. Un generador puede reemplazar una lista cuando no necesitas conservar todos los valores simultáneamente.
Una función normal frente a un generador
Una función con return finaliza y devuelve un resultado. Una función con yield entrega un valor temporalmente y conserva su estado.
def numeros():
yield 1
yield 2
yield 3
generador = numeros()
print(next(generador))
print(next(generador))
print(next(generador))Cada llamada a next() reanuda la función hasta encontrar el siguiente yield. Cuando no quedan valores, se genera StopIteration. Los bucles for gestionan esa excepción automáticamente.
Por qué yield ahorra memoria
Una lista crea todos sus elementos de inmediato. Un generador produce cada elemento cuando se necesita.
def cuadrados(limite):
for numero in range(limite):
yield numero * numero
for cuadrado in cuadrados(1_000_000):
if cuadrado > 100:
break
print(cuadrado)El generador no calcula el millón de cuadrados. El bucle se detiene al superar 100 y el resto nunca se produce. Esta característica es especialmente útil para archivos, flujos de red, consultas paginadas y pipelines.
Leer archivos grandes línea por línea
from pathlib import Path
def lineas_no_vacias(ruta):
with Path(ruta).open(encoding="utf-8") as archivo:
for linea in archivo:
texto = linea.strip()
if texto:
yield texto
for linea in lineas_no_vacias("datos.txt"):
procesar(linea)Este patrón mantiene una sola línea principal en memoria. La guía para leer archivos gigantes sin bloquear Python presenta otras técnicas como procesamiento por lotes y lectura en chunks.
Generadores infinitos
Un generador puede representar una secuencia sin final siempre que el consumidor decida cuándo detenerse.
def contador(inicio=0):
actual = inicio
while True:
yield actual
actual += 1
for numero in contador(10):
print(numero)
if numero == 15:
breakNo conviertas un generador infinito a lista. La operación nunca terminaría y acabaría consumiendo la memoria disponible.
Expresiones generadoras
Las expresiones generadoras utilizan una sintaxis parecida a una comprensión, pero con paréntesis.
cuadrados = (numero * numero for numero in range(10))
print(sum(cuadrados))Una lista usa corchetes y conserva todos los valores; una expresión generadora usa paréntesis y los produce bajo demanda. Para conjuntos pequeños que serán reutilizados, una lista puede ser más práctica. Para secuencias grandes o una sola pasada, el generador suele ser mejor.
Encadenar transformaciones
Los generadores permiten crear pipelines donde cada etapa recibe y produce elementos.
def leer_numeros(ruta):
with open(ruta, encoding="utf-8") as archivo:
for linea in archivo:
yield int(linea.strip())
def solo_pares(numeros):
for numero in numeros:
if numero % 2 == 0:
yield numero
def duplicar(numeros):
for numero in numeros:
yield numero * 2
resultado = duplicar(solo_pares(leer_numeros("numeros.txt")))
for valor in resultado:
print(valor)Cada etapa procesa un elemento y lo entrega a la siguiente. La memoria se mantiene estable incluso cuando el archivo es grande.
Usar yield from
yield from delega la producción de valores a otro iterable o generador.
def recorrer_grupos(grupos):
for grupo in grupos:
yield from grupo
print(list(recorrer_grupos([[1, 2], [3, 4], [5]])))Esta sintaxis evita escribir un segundo bucle manual. También transmite operaciones avanzadas como send() y excepciones entre generadores, aunque esas capacidades rara vez son necesarias en código inicial.
Enviar valores con send()
Un generador puede recibir información mientras está pausado.
def acumulador():
total = 0
while True:
valor = yield total
if valor is None:
return
total += valor
g = acumulador()
print(next(g))
print(g.send(10))
print(g.send(5))Este mecanismo es potente, pero puede hacer que el flujo sea difícil de seguir. Para la mayoría de los proyectos, generadores simples que solo producen valores son más mantenibles.
Generadores y excepciones
Los recursos abiertos dentro de un generador deben cerrarse correctamente. Un bloque with es la opción más segura. Si el consumidor abandona antes de terminar, Python puede cerrar el generador, pero no conviene depender de detalles implícitos para recursos críticos.
def registros(ruta):
with open(ruta, encoding="utf-8") as archivo:
for linea in archivo:
try:
yield convertir(linea)
except ValueError as error:
registrar_error(error, linea)Decide si una línea inválida debe omitirse, detener el proceso o enviarse a otra salida. No captures excepciones generales sin registrar el problema.
Combinar generadores con itertools
El módulo itertools ofrece utilidades optimizadas como islice, chain, takewhile y groupby.
from itertools import islice
primeros_cinco = islice(contador(100), 5)
print(list(primeros_cinco))islice limita un iterable sin crear previamente toda la secuencia.
Medir rendimiento correctamente
Un generador no siempre es más rápido. Su principal ventaja suele ser la memoria y la capacidad de comenzar a producir resultados antes. Si necesitas comparar implementaciones, utiliza timeit y mide también el consumo de memoria. Recuerda consumir completamente el generador durante el benchmark; medir únicamente su creación sería engañoso.
Cuándo no usar yield
Una lista es más adecuada cuando necesitas acceder repetidamente por índice, conocer la longitud inmediatamente, ordenar varias veces o recorrer los valores en distintas ocasiones. Los generadores son de una sola pasada. Una vez consumidos, debes crear otro generador.
Tampoco uses yield solo para hacer el código más sofisticado. Si el conjunto tiene diez elementos y la lista expresa mejor la intención, la claridad es más importante que una optimización irrelevante.
Errores frecuentes
Los errores habituales son intentar reutilizar un generador agotado, aplicar len() directamente, convertir secuencias infinitas a lista y olvidar que el código interno no se ejecuta hasta comenzar la iteración. También es común ocultar errores porque una función generadora no ejecuta su cuerpo al ser llamada, sino al solicitar el primer elemento.
Fuentes oficiales
La referencia oficial de expresiones yield describe el comportamiento de generadores, yield from, send() y cierre. La guía funcional oficial de Python explica la evaluación perezosa y su relación con iteradores.
Conclusión
yield permite construir funciones que producen valores progresivamente y conservan su estado entre iteraciones. Es ideal para archivos grandes, secuencias infinitas y pipelines. Utilízalo cuando una sola pasada y el bajo consumo de memoria aporten valor real, y conserva las listas para colecciones pequeñas que deben reutilizarse o indexarse.





