Una list comprehension es una expresión que produce una lista nueva evaluando una fórmula por cada elemento de un iterable. La palabra clave es expresión: no es un bloque de código, es un valor que puedes asignar, pasar como argumento o anidar dentro de otra estructura. Esa distinción marca toda la diferencia respecto a un bucle for con append.
La sintaxis completa es [expresión for elemento in iterable if condición]. La parte if condición es opcional y actúa como filtro: solo los elementos que la satisfacen alimentan la expresión. El orden refleja exactamente cómo lo leerías en voz alta: “dame expresión para cada elemento en iterable, pero solo si se cumple condición“.
¿Por qué son más rápidas que un bucle equivalente? CPython las compila a un opcode especializado (LIST_APPEND dentro de un frame propio) que evita el overhead de buscar y llamar al método .append en cada iteración. No es una diferencia dramática para listas pequeñas, pero en colecciones de miles de elementos el margen es consistente y medible.
El momento para no usarlas es igualmente importante: si necesitas más de una línea para que sea legible, o si el cuerpo tiene efectos secundarios (logging, mutación de estado externo), un bucle for explícito comunica mejor la intención. Una comprehension con tres cláusulas if y una función lambda anidada no es pythónico, es un acertijo.
# Ejemplos completos de todas las variantes
# ── 1. Comprehension básica con filtro ──────────────────────────────────
numbers = range(20)
# Solo los cuadrados de números pares
even_squares = [n ** 2 for n in numbers if n % 2 == 0]
# [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]
# ── 2. Transformación de strings ────────────────────────────────────────
raw_tags = [" Python ", "Django\n", " REST "]
clean_tags = [tag.strip().lower() for tag in raw_tags]
# ['python', 'django', 'rest']
# ── 3. Comprehension anidada: aplanar una matriz ─────────────────────────
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# El primer `for` recorre filas; el segundo recorre columnas dentro de cada fila.
# El orden de los `for` es el mismo que escribirías con bucles convencionales.
flat = [cell for row in matrix for cell in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# ── 4. Comprehension anidada: transponer la matriz ───────────────────────
# Aquí la expresión en sí es una lista → lista de listas
transposed = [[row[col] for row in matrix] for col in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# ── 5. Dict comprehension ────────────────────────────────────────────────
words = ["apple", "banana", "cherry"]
word_lengths = {word: len(word) for word in words}
# {'apple': 5, 'banana': 6, 'cherry': 6}
# Invertir un diccionario (solo seguro si los valores son únicos)
original = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# ── 6. Set comprehension ─────────────────────────────────────────────────
sentence = "el gato come el gato come pescado"
# Los sets eliminan duplicados automáticamente
unique_words = {word for word in sentence.split()}
# {'el', 'gato', 'come', 'pescado'} (orden no garantizado)
# ── 7. Cuándo el bucle for es la elección correcta ───────────────────────
import logging
results = []
for n in numbers:
value = n ** 2
if value > 50:
logging.debug("Including %d", value) # efecto secundario → bucle for
results.append(value)
Desglose del código
El ejemplo 1 es la forma canónica. Fíjate en que n % 2 == 0 filtra antes de que se evalúe n ** 2; Python no calcula el cuadrado de los números impares. El orden de evaluación es filtro primero, expresión después.
En el ejemplo 3 la clave es que los for se leen de izquierda a derecha exactamente igual que dos bucles anidados. for row in matrix es el bucle exterior; for cell in row es el interior. Un error frecuente es invertirlos intuitivamente al escribir comprehensions “de adentro hacia afuera”.
El ejemplo 4 introduce comprehensions anidadas donde la expresión es ella misma una comprehension. Aquí ya vale la pena detenerse y preguntarse si el código sigue siendo legible para quien lo lea en tres meses. Si la respuesta es dudosa, asigna la comprehension interior a una función con nombre descriptivo.
Los ejemplos 5 y 6 muestran dict comprehensions y set comprehensions. La sintaxis es idéntica a una list comprehension salvo por las llaves, y en el caso del dict necesitas una expresión clave: valor. Son igual de rápidas que su equivalente de lista y producen el tipo correcto directamente, sin necesidad de construir una lista y luego convertirla con dict() o set().
El ejemplo 7 muestra el caso donde no debes usar una comprehension: cuando hay un efecto secundario (aquí, un logging.debug). Usar una comprehension con efectos secundarios solo para ahorrar líneas convierte el código en una trampa de mantenimiento.
Errores que debes conocer
Error: Invertir el orden de los for en una comprehension anidada de aplanado.
matrix = [[1, 2], [3, 4]] # ❌ Wrong — intenta iterar números enteros como si fueran iterables flat_wrong = [cell for cell in row for row in matrix] # NameError o TypeError # ✅ Right — el for exterior va primero, igual que en bucles convencionales flat_right = [cell for row in matrix for cell in row] # [1, 2, 3, 4]
El orden de los for dentro de una comprehension sigue la misma secuencia que escribirías los bucles de afuera hacia adentro.
Error: Usar una list comprehension cuando el objetivo es solo iterar (desperdiciar memoria construyendo una lista que nadie usa).
# ❌ Wrong — construye una lista completa solo para sumarla y descartarla total = sum([n ** 2 for n in range(1_000_000)]) # ✅ Right — una generator expression hace lo mismo sin materializar la lista total = sum(n ** 2 for n in range(1_000_000))
Cuando pasas una comprehension como único argumento a una función como sum, max o any, quita los corchetes: obtienes una generator expression que produce valores uno a uno sin ocupar memoria adicional.
Error: Poner efectos secundarios dentro de una comprehension esperando que se ejecuten en orden garantizado.
log = []
# ❌ Wrong — funciona, pero abusa de la comprehension como bucle disfrazado
_ = [log.append(x) for x in range(5)]
# ✅ Right — si el propósito es el efecto secundario, usa un bucle for
for x in range(5):
log.append(x)
Una comprehension existe para producir un valor; si el valor resultante se descarta con _, la intención del código se vuelve opaca y cualquier linter serio te lo va a marcar.
N° 46