Python

Python

Tutorial Python: Generadores

Aprende a usar generadores y expresiones generadoras en Python para optimizar memoria y rendimiento en tus programas.

Aprende Python y certifícate

Yield

La palabra clave yield es el corazón de los generadores en Python, una característica que permite crear iteradores de forma elegante y eficiente. A diferencia de las funciones normales que devuelven un valor y terminan su ejecución, las funciones con yield pueden pausar su ejecución, entregar un valor, y luego reanudar desde donde se quedaron cuando se les solicita el siguiente valor.

Funciones con yield vs funciones normales

Veamos la diferencia fundamental entre una función normal y una función generadora:

# Función normal que devuelve una lista completa
def crear_lista(n):
    numeros = []
    for i in range(n):
        numeros.append(i)
    return numeros

# Función generadora que produce valores uno a uno
def crear_generador(n):
    for i in range(n):
        yield i

Cuando llamamos a crear_lista(1000000), Python crea inmediatamente una lista con un millón de elementos en memoria. En cambio, al llamar a crear_generador(1000000), obtenemos un objeto generador que no contiene ningún valor calculado todavía—solo la "receta" para producirlos cuando se necesiten.

Anatomía de un generador

Un generador se crea simplemente usando la palabra clave yield dentro de una función:

def contador(max):
    n = 0
    while n < max:
        yield n
        n += 1

Cuando llamamos a esta función, no ejecuta el código inmediatamente:

# Esto no ejecuta el código dentro de contador
gen = contador(5)
print(type(gen))  # <class 'generator'>

El generador permanece inactivo hasta que solicitamos valores mediante:

  • Un bucle for
  • La función next()
  • Cualquier otra operación que consuma iteradores
# Consumiendo el generador con un bucle for
for valor in contador(5):
    print(valor)  # Imprime 0, 1, 2, 3, 4

# Consumiendo el generador con next()
gen = contador(3)
print(next(gen))  # 0
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # StopIteration exception

El ciclo de vida de un generador

Para entender mejor cómo funciona yield, veamos el ciclo de vida de un generador:

def ejemplo_yield():
    print("Inicio de la función")
    yield 1
    print("Después del primer yield")
    yield 2
    print("Después del segundo yield")
    yield 3
    print("Fin de la función")

gen = ejemplo_yield()

Ahora, cada vez que llamamos a next(gen):

valor = next(gen)  # Imprime "Inicio de la función" y devuelve 1
print(f"Recibido: {valor}")

valor = next(gen)  # Imprime "Después del primer yield" y devuelve 2
print(f"Recibido: {valor}")

valor = next(gen)  # Imprime "Después del segundo yield" y devuelve 3
print(f"Recibido: {valor}")

# La siguiente llamada lanzará StopIteration
try:
    valor = next(gen)
except StopIteration:
    print("El generador se ha agotado")

Este comportamiento demuestra cómo el generador mantiene su estado entre llamadas, recordando exactamente dónde se quedó.

Ventajas de memoria

Una de las principales ventajas de los generadores es su eficiencia en memoria. Comparemos el uso de memoria entre una lista y un generador:

import sys

# Lista vs generador para un millón de números
numeros_lista = [i for i in range(1000000)]
numeros_gen = (i for i in range(1000000))  # Expresión generadora (veremos más en la siguiente sección)

# Comparación de tamaño en memoria
print(f"Tamaño de la lista: {sys.getsizeof(numeros_lista)} bytes")
print(f"Tamaño del generador: {sys.getsizeof(numeros_gen)} bytes")

La diferencia es dramática: la lista ocupa varios megabytes, mientras que el generador ocupa solo unos pocos bytes, independientemente de cuántos valores pueda producir.

Casos de uso prácticos

Los generadores son especialmente útiles en estos escenarios:

  • Procesamiento de archivos grandes:
def leer_archivo_por_lineas(nombre_archivo):
    with open(nombre_archivo, 'r') as archivo:
        for linea in archivo:
            yield linea.strip()

# Procesar un archivo gigante línea por línea sin cargarlo completo en memoria
for linea in leer_archivo_por_lineas('datos_enormes.csv'):
    # Procesar cada línea individualmente
    pass
  • Secuencias infinitas:
def numeros_pares_infinitos():
    n = 0
    while True:
        yield n
        n += 2

# Obtener los primeros 5 números pares
pares = numeros_pares_infinitos()
for _ in range(5):
    print(next(pares))  # Imprime 0, 2, 4, 6, 8
  • Cálculos bajo demanda:
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Obtener los primeros 10 números de Fibonacci
fib = fibonacci()
for _ in range(10):
    print(next(fib))  # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Yield con valores múltiples

Podemos usar yield múltiples veces en una función para crear secuencias complejas:

def rango_personalizado(inicio, fin, paso):
    # Validación de parámetros
    if paso == 0:
        raise ValueError("El paso no puede ser cero")
    
    # Determinar la dirección
    if paso > 0:
        while inicio < fin:
            yield inicio
            inicio += paso
    else:
        while inicio > fin:
            yield inicio
            inicio += paso  # Paso negativo

# Uso con diferentes parámetros
for i in rango_personalizado(0, 10, 2):
    print(i)  # 0, 2, 4, 6, 8

for i in rango_personalizado(10, 0, -3):
    print(i)  # 10, 7, 4, 1

Yield en bucles anidados

Podemos usar yield dentro de bucles anidados para generar secuencias más complejas:

def matriz_plana(matriz):
    """Convierte una matriz bidimensional en una secuencia plana de elementos."""
    for fila in matriz:
        for elemento in fila:
            yield elemento

# Ejemplo de uso
matriz = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

for valor in matriz_plana(matriz):
    print(valor)  # Imprime 1, 2, 3, 4, 5, 6, 7, 8, 9

Rendimiento y consideraciones

Los generadores no solo ahorran memoria, sino que también pueden mejorar el rendimiento en ciertos escenarios:

  • Permiten comenzar a procesar datos sin esperar a que se genere toda la secuencia
  • Reducen la latencia en aplicaciones interactivas
  • Son ideales para procesamiento de streams y datos en tiempo real

Sin embargo, es importante recordar que:

  • Los generadores solo pueden recorrerse una vez
  • No tienen acceso aleatorio como las listas (no puedes hacer generador[5])
  • No conocen su longitud sin consumirse (no puedes hacer len(generador))
# Ejemplo de consumo único
gen = (x for x in range(5))
for i in gen:
    print(i)  # Imprime 0, 1, 2, 3, 4

# Intentar recorrer de nuevo
for i in gen:
    print(i)  # No imprime nada, el generador ya está agotado

Para casos donde necesites recorrer los datos múltiples veces, puedes:

  1. Recrear el generador
  2. Convertirlo a una lista (si la memoria lo permite)
  3. Usar itertools.tee para clonar el generador
import itertools

gen = (x for x in range(5))
gen1, gen2 = itertools.tee(gen, 2)  # Crea dos copias del generador

# Ahora puedes usar gen1 y gen2 independientemente

Los generadores con yield son una herramienta fundamental en Python para trabajar con secuencias de datos de manera eficiente, especialmente cuando se trata de conjuntos grandes o potencialmente infinitos.

Expresiones generadoras

Las expresiones generadoras son una forma concisa y elegante de crear generadores en Python sin necesidad de definir una función completa con yield. Funcionan de manera similar a las comprensiones de listas, pero en lugar de construir una lista completa en memoria, crean un objeto generador que produce valores bajo demanda.

La sintaxis de una expresión generadora es muy similar a la de una comprensión de lista, pero utilizando paréntesis en lugar de corchetes:

# Comprensión de lista (crea toda la lista en memoria)
lista = [x**2 for x in range(10)]

# Expresión generadora (crea un generador que calcula valores según se necesitan)
generador = (x**2 for x in range(10))

Diferencias con las comprensiones de listas

La principal diferencia entre una expresión generadora y una comprensión de lista radica en cómo y cuándo se calculan los valores:

import sys

# Comparación de memoria para un millón de elementos
lista_comp = [i for i in range(1000000)]
gen_exp = (i for i in range(1000000))

print(f"Comprensión de lista: {sys.getsizeof(lista_comp)} bytes")
print(f"Expresión generadora: {sys.getsizeof(gen_exp)} bytes")

Al ejecutar este código, verás una diferencia dramática en el uso de memoria. La comprensión de lista almacena todos los valores a la vez, mientras que la expresión generadora solo mantiene la "receta" para producirlos.

Sintaxis y componentes

La sintaxis básica de una expresión generadora es:

(expresión for variable in iterable [if condición])

Donde:

  • expresión: Es la operación que se aplica a cada elemento
  • variable: Es la variable que toma cada valor del iterable
  • iterable: Es la secuencia de origen
  • condición: (Opcional) Filtra qué elementos se procesan

Veamos algunos ejemplos:

# Generador de cuadrados
cuadrados = (x**2 for x in range(10))

# Generador con filtro (solo números pares)
pares = (x for x in range(20) if x % 2 == 0)

# Generador con transformación y filtro
palabras = ['Python', 'es', 'genial', 'para', 'programar']
palabras_largas = (palabra.upper() for palabra in palabras if len(palabra) > 3)

Uso en contextos de iteración

Las expresiones generadoras se pueden usar directamente en cualquier contexto que espere un iterable:

# En un bucle for
for cuadrado in (x**2 for x in range(5)):
    print(cuadrado)  # Imprime 0, 1, 4, 9, 16

# Con la función sum
suma = sum(x for x in range(101))  # Suma de 0 a 100
print(suma)  # 5050

# Con la función max
max_cuadrado = max(x**2 for x in range(10))
print(max_cuadrado)  # 81

# Con la función sorted
ordenados = sorted(x % 5 for x in range(10))
print(ordenados)  # [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]

Cuando se usan con funciones como sum(), max(), min() o sorted(), ni siquiera es necesario incluir los paréntesis externos:

# Esto funciona igual que sum((x for x in range(101)))
suma = sum(x for x in range(101))

Expresiones generadoras anidadas

Al igual que las comprensiones de listas, las expresiones generadoras pueden anidarse:

# Generador de coordenadas (x, y) para una cuadrícula 5x5
coordenadas = ((x, y) for x in range(5) for y in range(5))

# Imprime todas las coordenadas
for coord in coordenadas:
    print(coord)  # (0,0), (0,1), ..., (4,4)

# Generador de pares de números donde ambos son pares
pares_de_pares = ((x, y) for x in range(10) if x % 2 == 0 
                         for y in range(10) if y % 2 == 0)

Encadenamiento de generadores

Una característica poderosa es la capacidad de encadenar operaciones sobre generadores:

# Encadenamiento de operaciones
numeros = range(100)
pares = (x for x in numeros if x % 2 == 0)
pares_al_cuadrado = (x**2 for x in pares)
divisibles_por_tres = (x for x in pares_al_cuadrado if x % 3 == 0)

# Consumir el generador final
for num in divisibles_por_tres:
    print(num)

Este enfoque permite crear pipelines de procesamiento eficientes en memoria, donde cada paso transforma o filtra los datos del paso anterior.

Casos de uso prácticos

Las expresiones generadoras brillan en varios escenarios comunes:

  • Procesamiento de archivos grandes:
# Contar líneas no vacías en un archivo grande
with open('archivo_grande.txt', 'r') as f:
    num_lineas = sum(1 for linea in f if linea.strip())
    print(f"El archivo tiene {num_lineas} líneas no vacías")
  • Transformación de datos:
# Extraer y normalizar datos de una lista de diccionarios
usuarios = [
    {'nombre': 'Ana', 'edad': 28, 'activo': True},
    {'nombre': 'Juan', 'edad': 32, 'activo': False},
    {'nombre': 'María', 'edad': 25, 'activo': True}
]

# Obtener nombres de usuarios activos en mayúsculas
nombres_activos = (usuario['nombre'].upper() 
                  for usuario in usuarios 
                  if usuario['activo'])

print(list(nombres_activos))  # ['ANA', 'MARÍA']
  • Cálculos estadísticos:
# Calcular estadísticas de una secuencia de números
datos = [12, 45, 23, 67, 89, 34, 29, 56]

# Usando generadores para estadísticas
promedio = sum(datos) / len(datos)
desviaciones = ((x - promedio)**2 for x in datos)
varianza = sum(desviaciones) / len(datos)
desviacion_estandar = varianza**0.5

print(f"Promedio: {promedio}")
print(f"Desviación estándar: {desviacion_estandar}")

Expresiones generadoras vs. funciones generadoras

Aunque ambas crean generadores, hay diferencias importantes:

  • Expresiones generadoras:
  • Sintaxis concisa, ideal para operaciones simples
  • Limitadas a una sola expresión
  • No pueden contener múltiples yield o lógica compleja
  • Funciones generadoras (con yield):
  • Permiten lógica más compleja y control de flujo
  • Pueden contener múltiples yield
  • Pueden mantener estado más sofisticado entre iteraciones

Elegir entre una u otra depende de la complejidad de la tarea:

# Tarea simple: mejor con expresión generadora
cuadrados = (x**2 for x in range(10))

# Tarea compleja: mejor con función generadora
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

Rendimiento y optimización

Las expresiones generadoras son muy eficientes, pero hay algunas consideraciones importantes:

  • Evaluación perezosa: Los valores solo se calculan cuando se solicitan, lo que ahorra memoria y puede mejorar el rendimiento.
# Este generador nunca calcula todos los valores
numeros = (x for x in range(1000000000))  # No consume mucha memoria
print(next(numeros))  # 0
print(next(numeros))  # 1
  • Uso único: Al igual que todos los generadores, una expresión generadora solo puede consumirse una vez.
gen = (x for x in range(5))
print(list(gen))  # [0, 1, 2, 3, 4]
print(list(gen))  # [] - El generador ya está agotado
  • Combinación con itertools: Para operaciones más avanzadas, las expresiones generadoras se combinan bien con el módulo itertools:
import itertools

# Generar pares de elementos adyacentes
numeros = [1, 2, 3, 4, 5]
pares = ((a, b) for a, b in zip(numeros, itertools.islice(numeros, 1, None)))
print(list(pares))  # [(1, 2), (2, 3), (3, 4), (4, 5)]

# Generar ventanas deslizantes de tamaño 3
ventanas = (tuple(itertools.islice(numeros, i, i+3)) 
           for i in range(len(numeros)-2))
print(list(ventanas))  # [(1, 2, 3), (2, 3, 4), (3, 4, 5)]

Expresiones generadoras en argumentos de funciones

Una característica útil es que cuando una expresión generadora es el único argumento de una función, los paréntesis externos pueden omitirse:

# Estas dos líneas son equivalentes
suma1 = sum((x**2 for x in range(10)))
suma2 = sum(x**2 for x in range(10))

# Lo mismo aplica para otras funciones
maximo = max(len(palabra) for palabra in ["Python", "es", "genial"])
minimo = min(x*x for x in range(1, 10))

Esta sintaxis hace que el código sea más limpio y legible cuando se trabaja con funciones que aceptan iterables.

Las expresiones generadoras representan una herramienta fundamental en el arsenal de Python para trabajar con datos de manera eficiente, permitiendo procesar grandes volúmenes de información con un mínimo impacto en la memoria del sistema.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Python online

Ejercicios de esta lección Generadores

Evalúa tus conocimientos de esta lección Generadores con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender el funcionamiento de la palabra clave yield y cómo crea generadores.
  • Diferenciar entre funciones normales y funciones generadoras.
  • Aprender a usar expresiones generadoras para crear iteradores de forma concisa.
  • Identificar casos prácticos donde los generadores mejoran el rendimiento y uso de memoria.
  • Conocer las limitaciones y consideraciones al trabajar con generadores y expresiones generadoras.