Skip to main content

Colecciones perezosas en Python

⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.


r8vnhill/

En Python, los generadores ofrecen una manera elegante y eficiente de construir iteradores que producen valores bajo demanda. A diferencia de las colecciones tradicionales, no almacenan todos los elementos en memoria, sino que generan cada valor en el momento en que se necesita. Esto permite procesar grandes volúmenes de datos o incluso secuencias infinitas con un uso mínimo de recursos, lo que los convierte en una herramienta fundamental para escribir código más ligero, expresivo y escalable.

⚙️ Características principales de los generadores en Python

  1. Creación Simple: Los generadores se definen como funciones normales que contienen al menos una instrucción yield.
  2. Ejecución Perezosa: Los valores se generan y devuelven uno a uno a medida que se solicitan, lo que ahorra memoria y mejora el rendimiento.
  3. Iteración Fácil: Los generadores son iterables y pueden ser utilizados en bucles for, comprensiones de listas, y otras construcciones iterativas de Python.
  4. Estado Persistente: Los generadores mantienen su estado entre las llamadas, permitiendo continuar la generación de valores donde se dejó.

🌀 Ejemplo de un Generador en Python

A continuación, se muestra cómo implementar un generador en Python que produce una secuencia infinita de números pares, similar a la implementación de Kotlin presentada anteriormente:

from typing import Generator, List

def even_numbers() -> Generator[int, None, None]:
number: int = 0
while True:
yield number
number += 2

# Uso del generador
gen: Generator[int, None, None] = even_numbers()
first_ten_evens: List[int] = [next(gen) for _ in range(10)]
print(first_ten_evens) # Output: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

🧠 Otras formas de crear generadores

Además de usar funciones con yield, Python también permite crear generadores en una sola línea usando expresiones generadoras, que se parecen a las comprensiones de listas pero utilizan paréntesis en lugar de corchetes.

# List comprehension: crea una lista completa en memoria
doubles = [2 * n for n in range(50)]

# Generador equivalente: produce los valores uno a uno, bajo demanda
doubles = (2 * n for n in range(50))
¿Qué acabamos de hacer?

La primera línea crea una lista entera con los 50 valores generados, lo que puede consumir mucha memoria si el rango es muy grande. En cambio, la segunda línea no crea la lista completa, sino que devuelve un generador que calcula cada valor solo cuando se necesita. Esto permite evaluación perezosa con una sintaxis compacta.

Puedes convertir ese generador en una lista cuando lo necesites:

doubles_list = list(doubles)

Esto es útil cuando primero quieres trabajar con un flujo perezoso, pero eventualmente necesitas materializar los datos.

📊 Resumen comparativo

AspectoPythonKotlin
Nombre del mecanismoGeneradores (yield, expresiones generadoras)Secuencias (sequence {}, generateSequence())
EvaluaciónPerezosa (lazy)Perezosa (lazy)
Sintaxis principalFunción con yieldBloque sequence {} o función generateSequence()
Alternativas de creaciónExpresiones generadoras con paréntesis (x for x in ...)No tiene una alternativa tan concisa como expresión literal
Estado internoMantenido automáticamenteMantenido automáticamente
TipadoDinámico (Generator, sin tipos en tiempo de compilación)Estático (Sequence<T>)
Integración con el lenguajeTotal: soportado en bucles, comprensiones, funciones nativas (next())Total: interoperable con colecciones y APIs estándar
Recolección de elementoslist(generator).toList()
Encadenamiento de operacionesLimitado (requiere funciones externas como map, filter, etc.)Rico en funciones intermedias y terminales encadenables (map, take)
Facilidad de comprensiónMuy accesible para principiantesRequiere comprensión de lambdas y API funcional
VentajasSencillez, expresividad, bajo consumo de memoriaTipado seguro, rendimiento optimizado en JVM
LimitacionesFalta de tipado estático, menos expresividad para transformar flujosMás verboso al declarar secuencias simples

✅ Beneficios y ❌ limitaciones

Beneficios

  • Evaluación perezosa eficiente: Los generadores calculan valores solo cuando se necesitan, lo que permite trabajar con flujos infinitos o grandes cantidades de datos sin agotar la memoria.
  • Simplicidad y expresividad: Son fáciles de escribir y entender, especialmente usando yield o expresiones generadoras con paréntesis.
  • Excelente integración con el lenguaje: Funcionan de forma nativa con bucles for, funciones como next(), y estructuras como comprensiones de listas.
  • Control total del flujo: Al mantener su estado entre invocaciones, permiten pausar y reanudar la ejecución de manera intuitiva.
  • Evitan colecciones intermedias: Reducen la creación de estructuras temporales al encadenar operaciones.

Limitaciones

  • Falta de tipado estático: Aunque se puede usar typing.Generator, el sistema de tipos no es tan estricto ni expresivo como en lenguajes como Kotlin.
  • Una sola pasada: Un generador solo puede iterarse una vez; una vez agotado, no puede reiniciarse sin volver a crearlo.
  • Menor soporte para operaciones encadenables: No poseen una API rica de transformaciones encadenadas como Sequence en Kotlin; se requiere usar funciones externas o itertools.
  • Depuración más difícil: Como la ejecución es diferida, puede ser más difícil rastrear errores o entender el estado interno durante la ejecución.
  • No tan adecuados para transformaciones complejas: Para operaciones que requieren múltiples pasos o reutilización, puede ser preferible usar listas o estructuras más explícitas.

🎯 Conclusiones

En esta lección exploramos el concepto de colecciones perezosas en Python, centrándonos en los generadores como mecanismo principal para implementar flujos eficientes de datos. Aprendimos cómo se definen, cómo funcionan internamente y cómo se pueden utilizar tanto mediante funciones con yield como con expresiones generadoras.

Los generadores en Python ofrecen una solución elegante para trabajar con datos que no necesitan ser evaluados inmediatamente, lo cual es ideal para flujos infinitos, procesamiento en streaming o contextos donde se busca minimizar el uso de memoria.

🔑 Puntos clave:

  • Evaluación perezosa: Los generadores calculan cada elemento a medida que se solicita, permitiendo eficiencia tanto en tiempo como en memoria.
  • Expresiones generadoras: Son una alternativa compacta y expresiva a las funciones con yield, y se comportan de forma completamente perezosa.
  • Una sola iteración: Un generador no puede reiniciarse una vez agotado, lo que requiere planificación si se desea reutilizar la secuencia.
  • Tipado dinámico: Python no impone tipos en tiempo de compilación, lo que otorga flexibilidad pero también reduce la capacidad de verificación estática.

🧰 ¿Qué nos llevamos?

El uso de generadores en Python nos invita a repensar cómo procesamos datos: ya no es necesario cargar todo en memoria ni construir listas completas si solo necesitamos una parte de la información. Gracias a su evaluación perezosa, los generadores nos permiten escribir código más eficiente, más expresivo y más alineado con las necesidades reales del programa.

Además, su simplicidad sintáctica —ya sea mediante yield o expresiones generadoras— los hace accesibles incluso para quienes están dando sus primeros pasos en Python. Y sin embargo, su utilidad no se limita a casos simples: los generadores pueden ser una herramienta poderosa en flujos de datos complejos, en algoritmos que necesitan mantener estado, y en la construcción de pipelines livianos que respetan los recursos del sistema.

En definitiva, aprender a usar generadores no es solo incorporar una nueva técnica, sino adoptar una mentalidad que prioriza la eficiencia, la claridad y el control sobre los datos que realmente importan.

📖 Referencias

🔥 Recomendadas

🌐 Generators in Python. (2024, diciembre 18). GeeksforGeeks. https://www.geeksforgeeks.org/generators-in-python/