Cerramos el módulo con la construcción más característica de Python. En las lecciones anteriores habrás notado un patrón repetido: crear una lista nueva recorriendo otra — los precios del catálogo con IVA, los títulos en mayúsculas para las etiquetas, los libros que aún tienen stock. Ese patrón de tres o cuatro líneas (crear lista vacía, recorrer, transformar o filtrar, añadir) es tan frecuente que Python le dedica una sintaxis propia: las comprensiones de listas (list comprehensions), que lo condensan en una sola línea legible. Dominar cuándo usarlas — y cuándo no — es una de las señas de identidad del código "pythónico".
Contenido
- El patrón que vamos a comprimir
- Sintaxis básica:
[expresion for elemento in iterable] - Filtrar con
ifal final - Transformar con
if/elseen la expresión - Comprensiones anidadas (con moderación)
- Comprensión vs. bucle: comparación directa
- Cuándo NO usar una comprensión
- Un vistazo a las comprensiones de diccionarios y conjuntos
El patrón que vamos a comprimir
Ana quiere la lista de precios del catálogo con el IVA de libros incluido. Con lo que sabes de la lección de bucles, lo harías así:
IVA_LIBROS = 0.04
precios = [12.50, 9.95, 15.90]
precios_con_iva = [] # 1. lista vacía de partida
for precio in precios: # 2. recorrer la original
precios_con_iva.append(precio * (1 + IVA_LIBROS)) # 3. transformar y añadir
print(precios_con_iva)Salida:
Aparece aquí append(), el método que añade un elemento al final de una lista — lo estudiaremos en detalle en el módulo 4; por ahora basta con esa idea. El patrón completo tiene siempre las mismas tres piezas: lista vacía → bucle → append de una transformación. Python permite expresar exactamente eso en una línea.
Sintaxis básica: [expresion for elemento in iterable]
La misma lista de precios con IVA, como comprensión:
IVA_LIBROS = 0.04
precios = [12.50, 9.95, 15.90]
precios_con_iva = [precio * (1 + IVA_LIBROS) for precio in precios]
print(precios_con_iva) # [13.0, 10.348, 16.536]Anatomía de la línea, de dentro afuera:
| Pieza | En el ejemplo | Papel |
|---|---|---|
for elemento in iterable |
for precio in precios |
El mismo encabezado de bucle que ya conoces |
expresion |
precio * (1 + IVA_LIBROS) |
Qué se guarda en la lista nueva por cada elemento |
[ ... ] |
los corchetes | Indican "construye una lista con todo esto" |
Se lee de forma casi natural: "una lista con precio * (1 + IVA) para cada precio en precios". El orden de escritura sorprende al principio (la expresión va antes que el for), pero refleja lo que te interesa: primero qué obtienes, después de dónde sale.
Más ejemplos rápidos con material de Papyrus:
titulos = ["La Odisea", "Hamlet", "El Quijote"]
# Títulos en mayúsculas para las etiquetas de las estanterías
etiquetas = [titulo.upper() for titulo in titulos]
# ['LA ODISEA', 'HAMLET', 'EL QUIJOTE']
# Longitud de cada título (para calcular el ancho de la tabla del menú)
longitudes = [len(titulo) for titulo in titulos]
# [9, 6, 10]
# Los primeros 5 números de ticket del día
tickets = [f"T-{n:03d}" for n in range(1, 6)]
# ['T-001', 'T-002', 'T-003', 'T-004', 'T-005'](upper() pasa una cadena a mayúsculas; como lower() en la lección anterior, es un método de str que veremos a fondo en el módulo 4.) Observa el tercer ejemplo: el iterable puede ser un range, una cadena, otra lista... cualquier cosa recorrible con for. Y la expresión puede ser tan simple como el propio elemento ([x for x in datos], que copia la lista) o tan elaborada como una f-string.
Filtrar con if al final
Segunda pieza: a veces no quieres transformar todos los elementos, sino quedarte solo con algunos. Añade un if después del for:
Solo los elementos que cumplan la condición pasan a la lista nueva. El clásico de Papyrus: ¿qué libros tienen stock?
titulos = ["La Odisea", "Hamlet", "El Quijote", "Fausto"]
stocks = [4, 6, 8, 0]
disponibles = [titulos[i] for i in range(len(titulos)) if stocks[i] > 0]
print(disponibles) # ['La Odisea', 'Hamlet', 'El Quijote']Y combinando transformación + filtro en una sola comprensión — precios con IVA, pero solo de los libros que cuestan menos de 15 €:
IVA_LIBROS = 0.04
precios = [12.50, 9.95, 15.90]
economicos_con_iva = [p * (1 + IVA_LIBROS) for p in precios if p < 15]
print(economicos_con_iva) # [13.0, 10.348]Se lee: "el precio con IVA, para cada p en precios, si p < 15". El filtro se aplica primero (¿entra este elemento?) y la expresión después (¿qué guardo de él?). El Quijote (15.90) no supera el filtro, así que ni siquiera se calcula su IVA.
En el if puedes usar todo lo de la lección 02-01: condiciones compuestas (if p < 15 and p > 10), truthiness (if titulo para descartar cadenas vacías), not...
respuestas = ["Hamlet", "", "Fausto", ""]
# Limpiar entradas vacías de un formulario de pedidos
pedidos_validos = [r for r in respuestas if r]
print(pedidos_validos) # ['Hamlet', 'Fausto']Transformar con if/else en la expresión
Cuidado, este caso se confunde mucho con el anterior. El if al final filtra (algunos elementos no entran). Pero si quieres que todos los elementos entren, eligiendo entre dos valores según una condición, lo que necesitas es el operador ternario de la lección 02-01, colocado en la expresión:
Ejemplo: el listado del menú de Papyrus debe mostrar todos los títulos, marcando los agotados.
titulos = ["La Odisea", "Hamlet", "El Quijote", "Fausto"]
stocks = [4, 6, 8, 0]
listado = [
titulos[i] if stocks[i] > 0 else f"{titulos[i]} (AGOTADO)"
for i in range(len(titulos))
]
print(listado)
# ['La Odisea', 'Hamlet', 'El Quijote', 'Fausto (AGOTADO)']Las dos posiciones del if, frente a frente:
| Forma | Posición del if |
¿else? |
Efecto | Tamaño de la lista resultado |
|---|---|---|---|---|
[expr for x in datos if cond] |
Después del for |
No admite | Filtra elementos | ≤ que la original |
[a if cond else b for x in datos] |
En la expresión (ternario) | Obligatorio | Elige el valor de cada elemento | Igual que la original |
Truco mnemotécnico: if sin else al final = portero (decide quién entra); if con else al principio = transformador (decide en qué se convierte cada uno). Y sí, pueden combinarse ambos en una misma comprensión, aunque la legibilidad empieza a resentirse:
# Con descuento de socio los baratos, con IVA todos, y fuera los agotados
DESCUENTO_SOCIO = 0.05
precios = [12.50, 9.95, 15.90, 21.00]
stocks = [4, 6, 8, 0]
finales = [
round(p * (1 - DESCUENTO_SOCIO), 2) if p < 15 else round(p, 2)
for i, p in enumerate(precios)
if stocks[i] > 0
]
print(finales) # [11.88, 9.45, 15.9]Fíjate también en que una comprensión larga puede (y debe) partirse en varias líneas, alineando el for y el if — sigue siendo una sola expresión entre corchetes. Y en que enumerate() funciona dentro de las comprensiones igual que en los bucles normales.
Comprensiones anidadas (con moderación)
Igual que los bucles se anidan, una comprensión admite varios for seguidos. Equivale a bucles anidados leídos de izquierda a derecha (el primer for es el bucle exterior):
# Todas las combinaciones formato-idioma que Papyrus puede pedir al distribuidor
formatos = ["tapa dura", "bolsillo"]
idiomas = ["es", "en"]
combinaciones = [f"{formato} ({idioma})" for formato in formatos for idioma in idiomas]
print(combinaciones)
# ['tapa dura (es)', 'tapa dura (en)', 'bolsillo (es)', 'bolsillo (en)']Su equivalente con bucles, para que veas la correspondencia exacta:
combinaciones = []
for formato in formatos: # primer for de la comprensión
for idioma in idiomas: # segundo for de la comprensión
combinaciones.append(f"{formato} ({idioma})")Otro uso del anidamiento es una comprensión dentro de otra, por ejemplo para generar una estructura tabular (una lista de listas):
# Rejilla de ubicaciones: 2 pasillos x 3 estanterías
ubicaciones = [[f"P{p}-E{e}" for e in range(1, 4)] for p in range(1, 3)]
print(ubicaciones)
# [['P1-E1', 'P1-E2', 'P1-E3'], ['P2-E1', 'P2-E2', 'P2-E3']]Advertencia sincera: dos for en una comprensión es el límite razonable. Tres o más, o anidamientos con filtros y ternarios a la vez, producen líneas que nadie (ni tú dentro de un mes) descifra a la primera. En esos casos, los bucles anidados clásicos de la lección 02-02 son la mejor herramienta. Comprimir no es el objetivo; comunicar, sí.
Comprensión vs. bucle: comparación directa
Pongamos las dos versiones lado a lado con el ejemplo completo de Papyrus — precios finales para Luis (socio) de los libros disponibles:
DESCUENTO_SOCIO = 0.05
IVA_LIBROS = 0.04
precios = [12.50, 9.95, 15.90, 21.00]
stocks = [4, 6, 8, 0]
# ---- Versión bucle: 5 líneas ----
precios_luis = []
for i, p in enumerate(precios):
if stocks[i] > 0:
final = p * (1 - DESCUENTO_SOCIO) * (1 + IVA_LIBROS)
precios_luis.append(round(final, 2))
# ---- Versión comprensión: 1 expresión ----
precios_luis = [
round(p * (1 - DESCUENTO_SOCIO) * (1 + IVA_LIBROS), 2)
for i, p in enumerate(precios)
if stocks[i] > 0
]
print(precios_luis) # [12.35, 9.83, 15.71] en ambos casos¿Qué gana cada versión?
| Criterio | Bucle for clásico |
Comprensión |
|---|---|---|
| Líneas de código | Más | Menos |
| Se lee como | Receta paso a paso | Declaración: "quiero esta lista" |
| Pasos intermedios (prints de depuración, varias operaciones) | Fáciles de añadir | No caben |
break/continue |
Disponibles | No existen dentro de una comprensión |
| Efectos colaterales (imprimir, modificar otras variables) | Naturales | Contraindicados |
| Velocidad | Buena | Ligeramente mayor (el append está optimizado internamente) |
La ventaja real de la comprensión no es ahorrar teclas: es declarar la intención. [t.upper() for t in titulos] dice "una lista con los títulos en mayúsculas" de un vistazo; el bucle equivalente obliga a leer tres líneas para deducir lo mismo. Por eso, para el patrón transformar/filtrar una colección en una lista nueva, la comprensión es la forma preferida en la comunidad Python.
Cuándo NO usar una comprensión
Tan importante como saber usarlas es saber contenerse. Señales de que toca volver al bucle clásico:
- No estás construyendo una lista. Si el objetivo es imprimir, acumular un total o pedir
input(), usa un bucle. Escribir[print(t) for t in titulos]funciona, pero crea una lista inútil deNoney confunde al lector: las comprensiones son para producir datos, no para hacer cosas. - La línea no cabe o no se entiende. Si necesitas más de dos o tres piezas (dos
for, un filtro y un ternario, por ejemplo) o superas con claridad los 79-99 caracteres de PEP 8 incluso partiéndola, un bucle con nombres intermedios será más claro. - Necesitas parar a medias o saltar elementos con lógica compleja. No hay
breaknicontinueen una comprensión; si los echas de menos, es un bucle. - Hay varios pasos dependientes. Calcular un valor, comprobarlo, ajustarlo y luego guardarlo son varias sentencias; embutirlas en una expresión suele requerir trucos ilegibles.
- Estás depurando. Un bucle admite
print()intermedios para inspeccionar valores; una comprensión, no. A veces conviene "desplegar" temporalmente una comprensión a bucle para investigar un problema y volver a plegarla después.
Regla final: escribe la comprensión, reléela como si la viera otra persona y, si tienes que pensar más de un par de segundos para entenderla, conviértela en bucle. Nadie ha reprochado jamás un bucle claro.
Un vistazo a las comprensiones de diccionarios y conjuntos
La misma idea funciona con otras estructuras de datos de Python que estudiarás en el módulo 4. Solo como aperitivo:
titulos = ["La Odisea", "Hamlet", "El Quijote"]
precios = [12.50, 9.95, 15.90]
# Comprensión de DICCIONARIO (llaves y dos puntos): título -> precio
tarifa = {titulos[i]: precios[i] for i in range(len(titulos))}
# {'La Odisea': 12.5, 'Hamlet': 9.95, 'El Quijote': 15.9}
# Comprensión de CONJUNTO (llaves sin dos puntos): iniciales únicas
iniciales = {titulo[0] for titulo in titulos}
# {'L', 'H', 'E'}Los diccionarios (parejas clave-valor) y los conjuntos (colecciones sin duplicados) merecen sus propias lecciones; cuando lleguemos a ellos en el módulo 4, sus comprensiones te resultarán familiares: son exactamente esta sintaxis con {} en lugar de []. De momento, quédate con que el patrón que has aprendido hoy es general.
Errores Comunes y Consejos
- Confundir el
if-filtro con elif/else-ternario:[x for x in datos if cond]filtra;[a if cond else b for x in datos]transforma. Escribir[x for x in datos if cond else y]esSyntaxError: eliffinal no admiteelse. - Usar comprensiones por sus efectos (
[print(x) for x in ...]): técnicamente válido, estilísticamente incorrecto. Si no vas a usar la lista resultante, es un bucle. - Sombrear variables: la variable de la comprensión es local a ella, pero si la llamas igual que otra variable del programa (
precio = 100y luego[precio for precio in precios]), el código se vuelve confuso. Usa nombres distintos y descriptivos. - Anidar de más: dos
fores el máximo cómodo. Si dudas, despliega a bucles. - Olvidar que produce una lista nueva: la comprensión no modifica la lista original;
preciossigue intacta tras[p * 2 for p in precios]. Si quieres conservar el resultado, asígnalo a una variable. - Consejo: cuando una comprensión se alargue, pártela en varias líneas dentro de los corchetes (expresión /
for/if, una pieza por línea). Python lo permite y la legibilidad mejora muchísimo. - Consejo: al leer una comprensión ajena, empieza por el
for(¿qué recorre?), sigue por elif(¿qué deja pasar?) y termina por la expresión (¿qué construye?). Es el orden en que se ejecuta.
Ejercicios
Ejercicio 1: PVP del catálogo
Dada precios = [12.50, 9.95, 15.90] y la constante IVA_LIBROS = 0.04, crea con una comprensión la lista pvp con cada precio con IVA redondeado a 2 decimales (usa round(valor, 2)). Después, con otra comprensión, crea pvp_socio aplicando además DESCUENTO_SOCIO = 0.05 sobre el precio base, solo para precios base inferiores a 15 € (los demás no deben aparecer en la lista).
Ejercicio 2: etiquetas del escaparate
Dadas titulos = ["La Odisea", "Hamlet", "El Quijote", "Fausto"] y stocks = [4, 6, 8, 0], crea con una sola comprensión la lista de etiquetas del escaparate: todos los títulos en mayúsculas, y los que estén agotados con el sufijo " - PROXIMAMENTE". Resultado esperado: ['LA ODISEA', 'HAMLET', 'EL QUIJOTE', 'FAUSTO - PROXIMAMENTE'].
Ejercicio 3: de comprensión a bucle (y criterio)
Esta comprensión funciona, pero es difícil de leer:
(a) Reescríbela como bucle for clásico con nombres de variables descriptivos. (b) Explica en una frase por qué en este caso el bucle es preferible. Nota: zip() empareja dos listas elemento a elemento — se estudia en el módulo 3; para este ejercicio te basta con saber que for t, s in zip(titulos, stocks) recorre título y stock a la vez.
Soluciones
Solución 1:
IVA_LIBROS = 0.04
DESCUENTO_SOCIO = 0.05
precios = [12.50, 9.95, 15.90]
pvp = [round(p * (1 + IVA_LIBROS), 2) for p in precios]
print(pvp) # [13.0, 10.35, 16.54]
pvp_socio = [
round(p * (1 - DESCUENTO_SOCIO) * (1 + IVA_LIBROS), 2)
for p in precios
if p < 15
]
print(pvp_socio) # [12.35, 9.83]La primera es una transformación pura (misma longitud que la original). La segunda combina filtro (if p < 15, que descarta El Quijote) y transformación (descuento sobre la base y luego IVA, el mismo orden de cálculo que usamos en la lección 02-01).
Solución 2:
titulos = ["La Odisea", "Hamlet", "El Quijote", "Fausto"]
stocks = [4, 6, 8, 0]
etiquetas = [
titulos[i].upper() if stocks[i] > 0 else titulos[i].upper() + " - PROXIMAMENTE"
for i in range(len(titulos))
]
print(etiquetas)
# ['LA ODISEA', 'HAMLET', 'EL QUIJOTE', 'FAUSTO - PROXIMAMENTE']Aquí el if/else va en la expresión (ternario) porque todos los títulos deben aparecer: no filtramos, elegimos el formato de cada uno. Versión aún más limpia, aplicando el sufijo con el ternario: [titulos[i].upper() + ("" if stocks[i] > 0 else " - PROXIMAMENTE") for i in range(len(titulos))].
Solución 3:
titulos = ["La Odisea", "Hamlet", "El Quijote", "Fausto"]
stocks = [4, 6, 8, 0]
resultado = []
for titulo, stock in zip(titulos, stocks):
if len(titulo) > 6 and stock != 1:
if stock > 3:
resultado.append(titulo.upper())
else:
resultado.append(titulo.lower())
print(resultado) # ['LA ODISEA', 'EL QUIJOTE'](b) El bucle es preferible porque la comprensión original acumula cuatro piezas (ternario + for + zip + filtro compuesto) en una línea: el lector debe descifrar a la vez qué filtra y qué transforma, mientras que el bucle separa cada decisión en su propia línea con nombres claros. Es exactamente el criterio de "cuándo NO usar una comprensión": si cuesta más de un par de segundos entenderla, despliégala.
Conclusión
Con las comprensiones de listas has cerrado el círculo de este módulo: [expresion for x in iterable if condicion] reúne en una sola línea declarativa lo que aprendiste por separado — la transformación de un bucle for, el filtro de un if y la elección de un ternario. Sabes filtrar (if al final), transformar (if/else en la expresión), anidar con prudencia y, sobre todo, reconocer cuándo un bucle clásico comunica mejor. También has visto que la misma sintaxis se extiende a diccionarios y conjuntos, un anticipo del módulo 4.
El módulo 2 termina aquí, y Papyrus lo nota: sus scripts ya deciden (if/elif/else), repiten (for, while), buscan y paran (break, else de bucle), conversan mediante menu.py (while True + match) y generan listas de precios y etiquetas en una línea. Pero todo ese código vive suelto, repetido de script en script: la búsqueda del menú, el cálculo del precio de socio, la validación de entradas... Cada mejora obliga a tocar varios sitios. La solución es empaquetar cada tarea con nombre propio y reutilizarla donde haga falta: eso son las funciones, protagonistas del módulo 3, donde reorganizaremos el menú de Papyrus pieza a pieza y descubrirás que ya has estado usando funciones (print, len, enumerate...) desde el primer día.
Curso de Programación en Python
Módulo 1: Introducción a Python
- Introducción a Python
- Configuración del Entorno de Desarrollo
- Sintaxis de Python y Tipos de Datos Básicos
- Variables y Constantes
- Entrada y Salida Básica
- Entornos Virtuales y Gestión de Paquetes
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for y while
- Herramientas de Control de Flujo
- Comprensiones de Listas
Módulo 3: Funciones y Módulos
- Definición de Funciones
- Argumentos de Función
- Funciones Lambda
- Módulos y Paquetes
- Visión General de la Biblioteca Estándar
Módulo 4: Estructuras de Datos
Módulo 5: Programación Orientada a Objetos
Módulo 6: Manejo de Archivos
- Lectura y Escritura de Archivos
- Trabajo con Archivos CSV
- Manejo de Datos JSON
- Operaciones con Archivos y Directorios
Módulo 7: Manejo de Errores y Excepciones
- Introducción a las Excepciones
- Manejo de Excepciones
- Lanzamiento de Excepciones
- Excepciones Personalizadas
- Buenas Prácticas y Registro de Errores con logging
Módulo 8: Temas Avanzados
- Anotaciones de Tipos (type hints)
- Decoradores
- Generadores
- Administradores de Contexto
- Concurrencia: Hilos y Procesos
- Asyncio para Programación Asíncrona
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con unittest
- Pruebas con pytest
- Desarrollo Guiado por Pruebas
- Técnicas de Depuración
- Uso de pdb para Depuración
Módulo 10: Desarrollo Web con Python
- Introducción al Desarrollo Web
- Fundamentos del Framework Flask
- Construcción de APIs REST con Flask
- Introducción a Django
- Construcción de Aplicaciones Web con Django
Módulo 11: Ciencia de Datos con Python
- Introducción a la Ciencia de Datos
- NumPy para Computación Numérica
- Pandas para Manipulación de Datos
- Matplotlib para Visualización de Datos
- Introducción al Aprendizaje Automático con scikit-learn
