AulaML Python · C++
Python intermedio · 35 min · Estructuras de datos

Recorridos y comprensiones

Transformar colecciones con enumerate, zip y expresiones que caben en una línea legible

Recorrer una colección es la operación más común al preparar datos. Aprenderás a obtener índice y valor con enumerate, a combinar listas con zip y a escribir comprensiones de lista, diccionario y conjunto que transforman y filtran sin perder legibilidad.

Lección 4 de 4 del módulo
Al terminar esta lección podrás
  • Recorrer listas, tuplas, diccionarios y conjuntos eligiendo el patrón adecuado
  • Usar enumerate para obtener posición y valor sin gestionar un contador manual
  • Combinar dos o más secuencias con zip y anticipar su comportamiento con longitudes distintas
  • Escribir comprensiones de lista, diccionario y conjunto con filtros
  • Decidir cuándo una comprensión mejora el código y cuándo conviene un bucle normal

Antes necesitas: Diccionarios y conjuntos · Bucles: while, for y control de iteraciones

En esta lección
  1. ¿Qué es una comprensión?
  2. ¿Para qué sirve?
  3. ¿Cómo funciona por dentro?
  4. Sintaxis: los cuatro patrones
  5. Ejemplo básico: enumerate y una primera comprensión
  6. Ejemplo intermedio: zip y comprensión de diccionario
  7. Caso práctico: limpiar un catálogo de productos
  8. Visualización: de dos listas a un diccionario filtrado
  9. Errores comunes
  10. Buenas prácticas y cuándo no usarlas
  11. Ejercicios
  12. Soluciones explicadas
  13. Resumen
  14. Evaluación

Preparar datos consiste, casi siempre, en recorrer una colección y producir otra: convertir precios a otra moneda, quedarse con los pedidos grandes, construir un diccionario de totales o eliminar duplicados. El bucle for resuelve todos esos casos, pero exige varias líneas para algo conceptualmente simple.

Python ofrece dos herramientas para ese trabajo. La primera es un conjunto de patrones de recorrido: enumerate cuando necesitas el índice, zip cuando necesitas combinar secuencias. La segunda son las comprensiones, expresiones que crean una colección nueva a partir de otra en una sola línea. Bien usadas, acercan el código a la pregunta que responde; mal usadas, lo vuelven jeroglífico.

¿Qué es una comprensión?

Una comprensión es una expresión que recorre un iterable, aplica una transformación a cada elemento y, opcionalmente, descarta los que no cumplen una condición. El resultado se recoge en una lista, un diccionario o un conjunto, según los delimitadores que uses. No es una función especial ni una sintaxis exótica: es azúcar para un bucle con append, escrita de forma que el qué queda delante del cómo.

La forma general es [expresión for elemento in iterable if condición]. Se lee de izquierda a derecha en dos pasadas: primero la expresión que produce cada valor y después el recorrido que lo alimenta. Ese orden, inverso al de un bucle, es la razón por la que al principio cuesta leerlas.

¿Para qué sirve?

  • Transformar: convertir una lista de precios en otra con IGV, o una lista de nombres en otra con formato de título.
  • Filtrar: quedarse solo con las notas aprobadas, los pedidos mayores a un umbral o los correos válidos.
  • Resumir: construir un diccionario de palabra a longitud o de categoría a total.
  • Eliminar duplicados: crear un conjunto a partir de una lista de identificadores.
  • Aplanar y combinar: recorrer pares de listas con zip y producir una estructura nueva.
  • Preparar features: derivar columnas calculadas antes de pasarlas a NumPy o Pandas.

¿Cómo funciona por dentro?

Al evaluar una comprensión, Python crea un ámbito nuevo, obtiene un iterador del iterable y pide elementos uno a uno. Para cada elemento evalúa la condición; si es verdadera, evalúa la expresión y añade el resultado a la colección que está construyendo. Al agotar el iterador, devuelve la colección completa. No hay magia: es el mismo trabajo que haría tu bucle, con menos texto y algo más de velocidad porque el cuerpo se ejecuta en bytecode especializado.

El orden de las cláusulas importa. En [x for x in datos if x > 0], el if actúa como filtro y decide si x entra. En cambio, [x if x > 0 else 0 for x in datos] no filtra: transforma cada elemento, sustituyendo los no positivos por cero. La primera forma puede reducir el tamaño; la segunda siempre produce la misma cantidad de elementos.

Si cambias los corchetes por paréntesis, (x * 2 for x in datos), no obtienes una tupla sino un generador: un objeto que produce valores bajo demanda y no los guarda en memoria. Es útil para sum, max o any, pero solo puedes recorrerlo una vez. Para materializarlo necesitas tuple(...) o list(...).

Sintaxis: los cuatro patrones

PYTHON comprensiones.py
numeros = [4, -2, 7]

# Lista: transformar y filtrar
dobles = [n * 2 for n in numeros if n > 0]

# Diccionario: clave y valor por elemento
cuadrados = {n: n ** 2 for n in numeros}

# Conjunto: valores únicos
signos = {n > 0 for n in numeros}

# Generador: se consume una sola vez
total = sum(n * n for n in numeros)

print(dobles)
print(cuadrados)
print(signos)
print(total)
  • Lista: [expr for x in it] produce una lista nueva y conserva el orden.
  • Diccionario: {clave: valor for x in it} exige dos expresiones separadas por dos puntos.
  • Conjunto: {expr for x in it} elimina duplicados y no garantiza orden.
  • Generador: (expr for x in it) no guarda nada; úsalo cuando solo vas a agregar.
  • El filtro if va al final; la expresión condicional a if cond else b va al principio.

Ejemplo básico: enumerate y una primera comprensión

PYTHON ventas.py
ventas = [120, 90, 150]

for i, monto in enumerate(ventas, start=1):
    print(f"Venta {i}: {monto}")

precios = [10, 25, 7]
con_igv = [p * 1.18 for p in precios]
print(con_igv)

enumerate(ventas, start=1) devuelve pares (1, 120), (2, 90) y (3, 150). El for los desempaqueta en i y monto, sin necesidad de inicializar un contador ni incrementarlo a mano. start=1 es una elección de presentación: para un usuario, la primera venta es la uno, aunque el índice interno empiece en cero.

La línea 7 es una comprensión de lista. Para cada p de precios, multiplica por 1.18 y añade el resultado a una lista nueva. El original no se toca. El resultado es [11.799999999999999, 29.5, 8.26]: los decimales binarios hacen que 11.8 no se represente exactamente, un detalle que resolverás con round o formato al mostrar, no dentro del cálculo.

Ejemplo intermedio: zip y comprensión de diccionario

zip recorre dos o más secuencias en paralelo y entrega una tupla por posición. Es la forma idiomática de unir nombres con notas sin indexar ambas listas. Si las longitudes no coinciden, se detiene en la más corta, sin avisar: conviene comprobarlas cuando los datos vienen de fuentes distintas.

PYTHON registro.py
nombres = ["Ana", "Luis", "Eva"]
notas = [14, 9, 17]

for nombre, nota in zip(nombres, notas):
    print(f"{nombre}: {nota}")

registro = dict(zip(nombres, notas))
aprobados = {n: v for n, v in registro.items() if v >= 13}
print(registro)
print(aprobados)

El primer for imprime tres líneas, una por par. dict(zip(...)) convierte esos pares en un diccionario: las claves son los nombres y los valores, las notas. La comprensión de la línea 8 recorre registro.items() y conserva solo los pares cuya nota llega a 13; el resultado es {'Ana': 14, 'Eva': 17}. Fíjate en la forma n: v antes del for: así se indica qué será clave y qué será valor.

Caso práctico: limpiar un catálogo de productos

Un catálogo exportado desde una hoja de cálculo llega con espacios sobrantes, mayúsculas inconsistentes y filas vacías. Antes de analizarlo hay que normalizar los nombres, descartar lo que quedó en blanco y calcular la longitud de cada nombre válido. Cada paso es una comprensión distinta y el conjunto se lee como una receta.

PYTHON catalogo.py
productos = ["Teclado", "  mouse ", "MONITOR", "teclado ", ""]

limpios = [p.strip().lower() for p in productos]
validos = [p for p in limpios if p]
largos = {p: len(p) for p in validos}
unicos = sorted(set(validos))

print(validos)
print(largos)
print(unicos)

La primera comprensión transforma: strip quita espacios y lower unifica mayúsculas. La segunda filtra: un string vacío es falso en un contexto booleano, así que if p lo descarta; es equivalente a if p != "" y más conciso. La tercera construye un diccionario de producto a longitud, donde las repeticiones se sobrescriben sin error porque la clave es la misma. La cuarta usa un conjunto para eliminar duplicados y sorted para devolver una lista estable.

El resultado de validos es ['teclado', 'mouse', 'monitor', 'teclado']; el diccionario queda con tres entradas y unicos es ['monitor', 'mouse', 'teclado']. Si en lugar de set hubieras usado dict.fromkeys, habrías conservado el orden de aparición.

Visualización: de dos listas a un diccionario filtrado

El siguiente programa combina zip, una comprensión de diccionario y un filtro. Antes de reproducirlo, predice qué contiene aprobados y cuántos elementos tendrá.

Ejecución visual ·zip, dict y filtro en cuatro líneas
1 / 6
1 nombres = ["Ana", "Luis", "Eva"]
2 notas = [14, 9, 17]
3 registro = dict(zip(nombres, notas))
4 aprobados = [n for n, v in registro.items() if v >= 13]
5 print(aprobados)
6 print(f"{len(aprobados)} de {len(registro)} aprobaron")
Se crea la lista de nombres.
Variables
nombres = ['Ana', 'Luis', 'Eva']
Salida
—

Avanza paso a paso: observa la línea resaltada, las variables y la salida.

La línea 4 condensa un bucle de cuatro líneas en una expresión, pero el orden de lectura no es el del código: primero se recorre registro.items(), luego se filtra por nota y al final se toma n. Si esa frase te resulta más clara que la expresión, probablemente el bucle sea la mejor opción para ese caso.

Errores comunes

Mensaje o síntomaPor qué ocurreCómo se arregla
SyntaxError al poner el filtro delanteEscribiste [x if x > 0 for x in xs]El filtro va después: [x for x in xs if x > 0]
Se obtiene [None, None]Usaste la comprensión solo para imprimir o mutarLos efectos secundarios van en un for normal
TypeError: 'generator' object is not subscriptableUsaste paréntesis y creíste que era una tuplaEnvuelve con tuple(...) o usa corchetes
Faltan elementos al combinar listaszip se detiene en la más cortaComprueba longitudes o usa zip_longest
La comprensión es ilegibleAnidaste dos for y varios ifDivide en pasos o vuelve al bucle
NameError con la variable del bucleIntentas usarla fuera de la comprensiónGuarda el resultado o usa un for normal
Filtrar no es lo mismo que transformar
Incorrecto
PYTHON SyntaxError
numeros = [-2, 3, -1, 5]
positivos = [x if x > 0 for x in numeros]
Correcto
PYTHON Correcto
numeros = [-2, 3, -1, 5]
positivos = [x for x in numeros if x > 0]
print(positivos)   # [3, 5]

La expresión condicional `x if x > 0 else ...` necesita un `else` y no filtra: devuelve un valor por cada elemento. Para descartar elementos, el `if` va al final, después del `for`. Si quisieras conservar la posición y sustituir los negativos por cero, sí usarías `[x if x > 0 else 0 for x in numeros]`.

Buenas prácticas y cuándo no usarlas

  • Usa una comprensión cuando la frase «para cada X, produce Y si Z» describa exactamente lo que hace.
  • Limítate a un for y, como mucho, un if; si necesitas más, el bucle gana en claridad.
  • No metas efectos secundarios: imprimir, escribir archivos o modificar otra lista dentro de una comprensión es una trampa.
  • Elige nombres que se lean: [precio * 1.18 for precio in precios] es mejor que [p * 1.18 for p in ps].
  • Si solo vas a sumar o buscar el máximo, usa un generador para no crear una lista intermedia.
  • Cuando dos pasos encadenados se entiendan peor que un bucle, quédate con el bucle: la legibilidad manda.
Comprensión frente a bucle en C++
Python
PYTHON cuadrados.py
numeros = [1, 2, 3, 4]
cuadrados = [n * n for n in numeros if n % 2 == 0]
print(cuadrados)
C++
CPP cuadrados.cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numeros{1, 2, 3, 4};
    std::vector<int> cuadrados;
    for (int n : numeros) {
        if (n % 2 == 0) cuadrados.push_back(n * n);
    }
    for (int c : cuadrados) std::cout << c << " ";
    std::cout << std::endl;
    return 0;
}

C++ no tiene comprensiones equivalentes en el lenguaje estándar; se usa un bucle con `push_back` o algoritmos de la STL. La ventaja de Python es expresiva, no mágica: por debajo se ejecuta un recorrido parecido.

Ejercicios

Ejercicio 1

Precios con descuento

Dada precios = [45, 120, 80, 250, 30], crea una lista con los precios mayores a 50 rebajados un 10 %. Muestra la lista resultante y cuántos productos quedaron fuera del descuento.
Pista

Combina una transformación p * 0.9 con un filtro if p > 50. Para contar los descartados, compara longitudes.

Completar código Completa la comprensión
numeros = [3, 8, 1, 10]
mayores = [n for  in numeros  n > 5]
cuadrados = {n:  for n in numeros}
print(mayores, cuadrados)

La variable del recorrido aparece dos veces: una en la expresión y otra en el `for`. El filtro se escribe después del iterable.

Ejercicio 2

Resumen de inventario

Tienes productos = ["teclado", "mouse", "monitor"] y precios = [120, 45, 300]. Construye un diccionario con los productos que cuestan más de 100, aplicando un 5 % de descuento a su precio. Después obtén la lista de nombres en mayúsculas y el total a pagar por esos productos.
Pista

Usa zip para unir ambas listas, una comprensión de diccionario con filtro y sum sobre los valores.

Soluciones explicadas

En el primer ejercicio, la comprensión hace dos cosas a la vez: filtra y transforma. El orden de las cláusulas garantiza que la multiplicación solo se aplique a los precios que pasan el filtro. Contar los descartados restando longitudes es más robusto que mantener un contador dentro de un bucle, porque no hay estado que actualizar.

En el segundo, la clave está en no separar lo que va junto: zip mantiene la correspondencia entre nombre y precio, y la comprensión construye el diccionario en una pasada. round(..., 2) evita arrastrar decimales binarios en la presentación, y sum(caros.values()) agrega sin crear una lista intermedia. Si el filtro o el descuento cambiaran, solo habría que tocar una línea.

Resumen

  • enumerate(coleccion, start=1) entrega índice y valor sin contadores manuales.
  • zip(a, b) recorre dos secuencias en paralelo y se detiene en la más corta.
  • Una comprensión de lista se escribe [expr for x in it if cond].
  • {k: v for ...} crea diccionarios y {expr for ...} crea conjuntos.
  • El if de filtrado va después del for; la expresión condicional va antes.
  • Los paréntesis crean un generador, no una tupla.
  • Si la comprensión necesita dos niveles o efectos secundarios, usa un bucle normal.

Evaluación

Evaluación 1

¿Qué produce [n * 2 for n in [1, 2, 3] if n != 2]?

Evaluación 2

¿Qué devuelve list(zip([1, 2, 3], ["a", "b"]))?

Evaluación 3

¿Cuándo es mejor un bucle for que una comprensión?

Tus notas

¿Ya lo entendiste?

Márcala como completada para llevar tu progreso y desbloquear el siguiente paso de la ruta.