Generadores en Python: cómo yield pausa y reanuda funciones
@programacionLo esencial
- yield convierte una función normal en un generador que se pausa y reanuda en cada llamada.
- El cuerpo de un generador no se ejecuta hasta que algo llama next() sobre él por primera vez.
- Una lista guarda todos sus elementos en memoria; un generador guarda solo el punto donde se quedó.
- Python implementa el protocolo de iteradores (__iter__ y __next__) automáticamente en cada generador.
- yield from delega la iteración en otro generador o iterable sin escribir un bucle manual.
- Al terminar el cuerpo de la función, el generador lanza StopIteration de forma automática.
- Las expresiones generadoras usan paréntesis en vez de corchetes: (x for x in datos) en vez de una lista.
Una lista con un millón de números ocupa varios megabytes de RAM apenas se crea. Un generador que produce esos mismos números ocupa unas pocas decenas de bytes, sin importar cuántos elementos vaya a entregar: la diferencia está en una sola palabra, yield.
Qué es un generador
Un generador es una función que, en lugar de devolver un único valor con return, va entregando valores uno por uno con yield. Cada vez que aparece yield, la función se congela en ese punto exacto: variables locales, posición en el código y todo su estado quedan guardados hasta la siguiente llamada.
La documentación oficial de Python describe este comportamiento en detalle en la referencia de expresiones yield. Ahí se explica que llamar a una función generadora no ejecuta su código: solo crea un objeto generador.
El protocolo de iteración: next() y StopIteration
Cada llamada a next() avanza el generador hasta el próximo yield y devuelve ese valor. Cuando el cuerpo de la función termina, Python lanza automáticamente la excepción StopIteration, que es justo la señal que usa un bucle for para saber cuándo parar.
def contador_regresivo(inicio):
while inicio > 0:
yield inicio
inicio -= 1
cuenta = contador_regresivo(3)
print(next(cuenta)) # 3
print(next(cuenta)) # 2
print(next(cuenta)) # 1
print(next(cuenta)) # lanza StopIterationEl bucle for hace ese trabajo por vos: llama a next() en cada vuelta y captura la excepción sin que el programador tenga que escribirla.
Ejemplo práctico: procesar un archivo grande sin cargarlo entero
El caso de uso más común de un generador es leer datos que no entran cómodos en memoria, como un archivo de log de varios gigabytes.
def leer_lineas_de_error(ruta_archivo):
with open(ruta_archivo) as archivo:
for linea in archivo:
if "ERROR" in linea:
yield linea.strip()
for linea_error in leer_lineas_de_error("logs_servidor.txt"):
print(linea_error)Esta función nunca tiene el archivo completo en memoria: abre el archivo, produce una línea filtrada por vez y lo cierra automáticamente al terminar, gracias al with.
Cómo comprobar el ahorro de memoria en tu propia máquina
No hace falta confiar en cifras ajenas: podés medirlo con sys.getsizeof, que devuelve el tamaño en bytes de un objeto en memoria.
import sys
lista_numeros = [numero for numero in range(1_000_000)]
generador_numeros = (numero for numero in range(1_000_000))
print(sys.getsizeof(lista_numeros))
print(sys.getsizeof(generador_numeros))El primer print muestra el tamaño real de una lista con un millón de enteros ya materializados. El segundo muestra el tamaño del objeto generador, que es constante sin importar cuántos elementos vaya a producir.
yield from: delegar en otro generador
Cuando un generador necesita entregar los valores de otro generador o iterable, yield from evita escribir un bucle manual con yield adentro.
def procesar_lotes(lotes):
for lote in lotes:
yield from lote
resultado = list(procesar_lotes([[1, 2, 3], [4, 5], [6]]))
print(resultado) # [1, 2, 3, 4, 5, 6]Sin yield from, la misma función necesitaría un bucle for interno con un yield explícito por cada elemento del lote.
Expresiones generadoras: la versión compacta
Para transformaciones simples, Python permite escribir un generador como una expresión, usando paréntesis en vez de corchetes.
cuadrados = (numero ** 2 for numero in range(1_000_000))
suma_total = sum(cuadrados)
print(suma_total)sum() consume el generador elemento por elemento, sin que exista nunca una lista completa de un millón de cuadrados en memoria.
Cuándo NO conviene usar un generador
Un generador se recorre una sola vez: una vez agotado, volver a iterarlo no produce nada más y hay que crear uno nuevo. Si el código necesita acceder a un elemento por índice, conocer la longitud con len() o recorrer los datos varias veces, una lista es la opción correcta.
Además, cada next() implica una llamada de función con su propio costo. Para colecciones pequeñas que ya caben cómodas en memoria, una lista por comprensión suele ser más simple y no más lenta en la práctica.
Conclusión
yield no es una sintaxis exótica: es la herramienta que separa producir datos de guardarlos todos de una vez. Cuando el volumen de datos es grande, desconocido de antemano o viene de un flujo externo como un archivo o una red, un generador resuelve el problema con menos memoria y menos código que gestionar índices a mano.
📖 Versión extendida con más detalle: https://elsolitario.org/2026/08/14/como-funciona-yield-generadores-python/?utm_source=telegraph&utm_medium=instant_view&utm_campaign=programacion