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

  1. El patrón que vamos a comprimir
  2. Sintaxis básica: [expresion for elemento in iterable]
  3. Filtrar con if al final
  4. Transformar con if/else en la expresión
  5. Comprensiones anidadas (con moderación)
  6. Comprensión vs. bucle: comparación directa
  7. Cuándo NO usar una comprensión
  8. 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:

[13.0, 10.348, 16.536]

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:

[expresion for elemento in iterable if condicion]

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:

[valor_si_true if condicion else valor_si_false for elemento in iterable]

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 de None y 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 break ni continue en 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 el if/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] es SyntaxError: el if final no admite else.
  • 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 = 100 y luego [precio for precio in precios]), el código se vuelve confuso. Usa nombres distintos y descriptivos.
  • Anidar de más: dos for es el máximo cómodo. Si dudas, despliega a bucles.
  • Olvidar que produce una lista nueva: la comprensión no modifica la lista original; precios sigue 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 el if (¿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:

r = [t.upper() if s > 3 else t.lower() for t, s in zip(titulos, stocks) if len(t) > 6 and s != 1]

(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

Módulo 2: Estructuras de Control

Módulo 3: Funciones y Módulos

Módulo 4: Estructuras de Datos

Módulo 5: Programación Orientada a Objetos

Módulo 6: Manejo de Archivos

Módulo 7: Manejo de Errores y Excepciones

Módulo 8: Temas Avanzados

Módulo 9: Pruebas y Depuración

Módulo 10: Desarrollo Web con Python

Módulo 11: Ciencia de Datos con Python

Módulo 12: Proyecto Final

© Copyright 2026. Todos los derechos reservados