Comprensiones de listas en Python
En esta lección
Muchas veces un bucle for solo sirve para construir una lista nueva a partir de otra: pasar precios a euros con IVA, quedarte con los aprobados, poner nombres en mayúsculas. Python tiene una sintaxis compacta para eso: las comprensiones (comprehensions). En esta lección aprenderás a escribirlas para listas, diccionarios y conjuntos, a usar expresiones generadoras con sum, any y all, y, igual de importante, a reconocer cuándo no conviene usarlas.
Necesitas tener claros los bucles y las listas, donde ya viste un primer ejemplo.
Del bucle for a la comprensión
Imagina una tienda que guarda los precios sin IVA y quiere calcularlos con el 21 % incluido. Con lo que ya sabes lo harías así:
precios = [10, 25, 8, 40]
con_iva = []
for p in precios:
con_iva.append(p * 1.21)
print(con_iva) # [12.1, 30.25, 9.68, 48.4]
Son tres líneas con un patrón que se repite muchísimo: crear una lista vacía, recorrer otra y hacer append. La comprensión hace lo mismo en una línea:
con_iva = [p * 1.21 for p in precios]
print(con_iva) # [12.1, 30.25, 9.68, 48.4]
La estructura es siempre:
[expresión for variable in iterable]
Se lee casi como una frase: “p * 1.21 para cada p en precios”. Fíjate en que la expresión va primero: es lo que acabará dentro de la lista nueva.
¿Por qué usarla? Porque dice de un vistazo qué se construye, sin tener que seguir el append por el bucle. Además es algo más rápida, porque Python la optimiza internamente. La lista original no se modifica: siempre se crea una nueva.
Más ejemplos de transformación:
nombres = ["ana", "luis", "marta"]
print([n.capitalize() for n in nombres]) # ['Ana', 'Luis', 'Marta']
print([len(n) for n in nombres]) # [3, 4, 5]
print([n * n for n in range(1, 6)]) # [1, 4, 9, 16, 25]
Sirve con cualquier cosa que se pueda recorrer: listas, range, cadenas, tuplas, diccionarios…
Filtrar con if
Si añades un if al final, solo entran los elementos que cumplen la condición:
notas = [7, 3, 9, 4.5, 5, 8]
aprobadas = [n for n in notas if n >= 5]
print(aprobadas) # [7, 9, 5, 8]
Equivale a:
aprobadas = []
for n in notas:
if n >= 5:
aprobadas.append(n)
Puedes filtrar y transformar a la vez, y también desempaquetar tuplas en la variable del bucle:
productos = [("teclado", 25), ("raton", 12), ("monitor", 180), ("cable", 5)]
baratos = [nombre for nombre, precio in productos if precio < 30]
print(baratos) # ['teclado', 'raton', 'cable']
Con cadenas es muy útil para limpiar texto:
texto = "Hola, qué tal 2024!"
solo_letras = "".join([c for c in texto if c.isalpha()])
print(solo_letras) # Holaquétal
Elegir un valor con if-else
A veces no quieres descartar elementos, sino transformar cada uno de una forma u otra. Para eso se usa la expresión condicional valor_si if condición else valor_no, y va al principio, como parte de la expresión:
notas = [7, 3, 9, 4.5, 5, 8]
resultado = ["aprobado" if n >= 5 else "suspenso" for n in notas]
print(resultado)
# ['aprobado', 'suspenso', 'aprobado', 'suspenso', 'aprobado', 'aprobado']
Esta es la duda más frecuente con las comprensiones, así que compáralas bien:
| Forma | Posición del if | Qué hace | Longitud del resultado |
|---|---|---|---|
[x for x in datos if cond] | Al final | Filtra: descarta elementos | Igual o menor |
[a if cond else b for x in datos] | Al principio, con else | Elige qué valor poner | Siempre igual |
Cuidado:
[n for n in notas if n >= 5 else 0]daSyntaxError. Elifdel final no admiteelse. Si necesitaselse, elifva delante delfor.
Comprensiones anidadas
Una comprensión puede tener varios for. Se leen de izquierda a derecha en el mismo orden que los bucles anidados.
Aplanar una lista de listas
grupos = [["Ana", "Luis"], ["Marta"], ["Pablo", "Sara", "Eva"]]
todos = [alumno for grupo in grupos for alumno in grupo]
print(todos) # ['Ana', 'Luis', 'Marta', 'Pablo', 'Sara', 'Eva']
Es exactamente lo mismo que:
todos = []
for grupo in grupos: # primer for
for alumno in grupo: # segundo for
todos.append(alumno)
Un truco para no liarte: escribe los for en el mismo orden en que los pondrías con bucles normales y pon la expresión delante.
Combinaciones
Dos for generan todas las combinaciones posibles, por ejemplo, las variantes de una camiseta:
tallas = ["S", "M", "L"]
colores = ["rojo", "azul"]
variantes = [f"{t}-{c}" for t in tallas for c in colores]
print(variantes)
# ['S-rojo', 'S-azul', 'M-rojo', 'M-azul', 'L-rojo', 'L-azul']
Matrices
Si metes una comprensión dentro de la expresión de otra, obtienes una lista de listas (una matriz). Aquí la tabla de multiplicar del 1 al 3:
tabla = [[f * c for c in range(1, 4)] for f in range(1, 4)]
print(tabla) # [[1, 2, 3], [2, 4, 6], [3, 6, 9]]
Y así se traspone una matriz (las filas pasan a ser columnas):
matriz = [[1, 2, 3],
[4, 5, 6]]
traspuesta = [[fila[i] for fila in matriz] for i in range(3)]
print(traspuesta) # [[1, 4], [2, 5], [3, 6]]
La comprensión es además la forma correcta de crear un tablero vacío. Compara:
tablero = [[0] * 3 for _ in range(3)]
tablero[0][0] = 1
print(tablero) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
malo = [[0] * 3] * 3
malo[0][0] = 1
print(malo) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
[[0] * 3] * 3 repite la misma fila tres veces: al cambiar una, cambian todas. La comprensión crea una fila nueva en cada vuelta. El _ es el nombre habitual para una variable que no vas a usar.
Comprensiones de diccionarios
Con llaves y la forma clave: valor construyes un diccionario:
nombres = ["Ana", "Luis", "Marta"]
longitudes = {n: len(n) for n in nombres}
print(longitudes) # {'Ana': 3, 'Luis': 4, 'Marta': 5}
Lo más habitual es recorrer otro diccionario con .items() para transformar o filtrar sus valores:
precios = {"pan": 1.2, "leche": 0.95, "cafe": 4.5}
subida = {p: round(v * 1.1, 2) for p, v in precios.items()}
print(subida) # {'pan': 1.32, 'leche': 1.04, 'cafe': 4.95}
baratos = {p: v for p, v in precios.items() if v < 2}
print(baratos) # {'pan': 1.2, 'leche': 0.95}
Invertir claves y valores también es inmediato:
codigos = {"ES": "España", "FR": "Francia"}
paises = {v: k for k, v in codigos.items()}
print(paises) # {'España': 'ES', 'Francia': 'FR'}
Y para juntar dos listas en un diccionario se usa zip:
alumnos = ["Ana", "Luis"]
notas = [8, 6]
print({a: n for a, n in zip(alumnos, notas)}) # {'Ana': 8, 'Luis': 6}
print(dict(zip(alumnos, notas))) # {'Ana': 8, 'Luis': 6}
Si no transformas nada, dict(zip(...)) es más sencillo.
Comprensiones de conjuntos
Con llaves pero sin los dos puntos obtienes un conjunto, que elimina duplicados automáticamente. Perfecto para limpiar datos:
emails = ["Ana@Mail.com", "ana@mail.com", "luis@mail.com", "LUIS@mail.com "]
unicos = {e.strip().lower() for e in emails}
print(sorted(unicos)) # ['ana@mail.com', 'luis@mail.com']
Los conjuntos no tienen orden, por eso usamos sorted para mostrar el resultado siempre igual.
Cuidado:
{}vacío es un diccionario, no un conjunto. Y no existe la “comprensión de tuplas”:(x for x in datos)con paréntesis es otra cosa, una expresión generadora.
Expresiones generadoras
Si cambias los corchetes por paréntesis, no se crea ninguna lista. Se crea un generador: un objeto que va produciendo los valores de uno en uno, cuando se le piden.
cuadrados = (n * n for n in range(4))
print(list(cuadrados)) # [0, 1, 4, 9]
print(list(cuadrados)) # [] (ya se ha agotado)
Un generador solo se puede recorrer una vez. A cambio, casi no ocupa memoria: una lista de un millón de números ocupa unos 8 MB, mientras que el generador equivalente ocupa unos 200 bytes, tenga los elementos que tenga.
Su uso estrella es pasarlo directamente a funciones como sum, max, min o "".join. Cuando el generador es el único argumento, puedes quitar los paréntesis extra:
carrito = [("camiseta", 15.0, 2), ("pantalon", 30.0, 1), ("calcetines", 4.5, 3)]
total = sum(precio * cantidad for _, precio, cantidad in carrito)
print(total) # 73.5
palabras = ["sol", "mar", "casa", "luna", "pez"]
print(max(len(p) for p in palabras)) # 4
Aquí no hace falta guardar la lista de importes: solo queremos la suma. Por eso sum(x for x in ...) es preferible a sum([x for x in ...]).
any y all
Dos funciones que combinan de maravilla con generadores:
any(...)devuelveTruesi al menos un elemento es verdadero.all(...)devuelveTruesi todos los elementos son verdaderos.
notas = [7, 3, 9, 4.5, 5, 8]
print(any(n < 5 for n in notas)) # True (hay algún suspenso)
print(all(n >= 5 for n in notas)) # False (no han aprobado todos)
contrasenia = "secreto123"
tiene_numero = any(c.isdigit() for c in contrasenia)
print(tiene_numero) # True
Además se detienen en cuanto conocen la respuesta: any para en el primer True y all en el primer False. Con una lista vacía, any([]) es False y all([]) es True.
Cuándo NO usar comprensiones
Las comprensiones son para construir una colección. No son una forma “más pro” de escribir cualquier bucle.
-
Si no vas a usar la lista resultante, usa un
for. Esto funciona, pero crea una lista deNoneque no sirve para nada:[print(n) for n in range(2)] # mal: resultado [None, None] for n in range(2): # bien print(n) -
Si la lógica es larga, usa un
for. Con más de dosfor, variosifo una expresión condicional anidada, el código se vuelve ilegible. Si tienes que leerla tres veces para entenderla, reescríbela. -
Si necesitas varias instrucciones por elemento (un
try, unprintde depuración, actualizar un contador), la comprensión no las admite. -
Si solo quieres un resultado (suma, máximo, comprobar algo), usa un generador dentro de
sum,max,anyoall, no una lista.
Consejo: si la transformación se complica, sácala a una función con nombre y úsala en la comprensión:
[calcular_precio(p) for p in productos]. Queda corta y se entiende.
Errores frecuentes
- Poner el
elseen el filtro:[x for x in datos if x > 0 else 0]es unSyntaxError. Elif-elseva delante delfor. - Invertir el orden de los
foranidados:[alumno for alumno in grupo for grupo in grupos]daNameError, porquegrupose usa antes de definirse. - Reutilizar un generador agotado: la segunda vez que lo recorres está vacío.
- Crear matrices con
[[0] * n] * n: todas las filas son la misma. Usa[[0] * n for _ in range(n)]. - Esperar orden en un conjunto:
{...}sin:no garantiza el orden de los elementos.
Resumen
| Quieres | Sintaxis | Ejemplo |
|---|---|---|
| Transformar | [expr for x in datos] | [p * 1.21 for p in precios] |
| Filtrar | [x for x in datos if cond] | [n for n in notas if n >= 5] |
| Elegir valor | [a if cond else b for x in datos] | ["par" if n % 2 == 0 else "impar" for n in nums] |
| Aplanar | [y for x in datos for y in x] | [a for g in grupos for a in g] |
| Diccionario | {k: v for ...} | {n: len(n) for n in nombres} |
| Conjunto | {expr for ...} | {e.lower() for e in emails} |
| Solo un resultado | sum(expr for ...) | sum(p * c for _, p, c in carrito) |
| ¿Alguno? ¿Todos? | any(...), all(...) | all(n >= 5 for n in notas) |
Toda comprensión equivale a un bucle for con append (o asignación en un diccionario, o add en un conjunto). Si alguna vez dudas, escríbela primero como bucle y conviértela después. En la próxima lección profundizarás en las funciones, que combinarás a menudo con las comprensiones.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.