Comprensiones en Python: list, dict, set y generator expressions

📅 Actualizado en marzo 2026 📊 Nivel: Principiante ⏱️ 17 min de lectura
Πάντα ἀριθμός ἐστιν
«Todo es número»
Pitágoras de Samos · Matemático y filósofo · ~570 a.C. – ~495 a.C.
Pitágoras fundó en Crotona una comunidad filosófica donde los números no eran solo herramientas de cálculo — eran la esencia de la realidad. Todo fenómeno observable, desde las proporciones musicales hasta las formas geométricas, podía expresarse en términos numéricos con suficiente elegancia. Las comprensiones de Python son, en ese sentido, el instrumento más pitagórico del lenguaje: toman una colección de elementos y los transforman en otra colección siguiendo una regla precisa y expresada en una sola línea. [x**2 for x in range(1, 11)] no es solo código eficiente — es una afirmación sobre el universo. Pitágoras habría añadido: y además es más rápido que el bucle for equivalente en un 30%. Quizás no. Pero habría apreciado la elegancia.

Las comprensiones son una de las características más idiomáticas de Python y una de las primeras que distinguen a un programador que viene de otro lenguaje de alguien que ya piensa en Python. En lugar de crear una lista vacía y rellenarla con un bucle for y append(), escribes la transformación completa en una sola expresión. Más legible, más rápido, y en muchos casos más cercano a la intención matemática de lo que quieres hacer.

💡 Qué es una comprensión y por qué usarla

Una comprensión es una sintaxis compacta para construir una nueva colección aplicando una expresión a cada elemento de un iterable, opcionalmente filtrando elementos. Existen cuatro variantes: list, dict, set, y generator expression.

# El mismo resultado, dos estilos distintos:

# ── Estilo imperativo (for + append): ──
cuadrados = []
for x in range(1, 6):
    cuadrados.append(x ** 2)
print(cuadrados)    # [1, 4, 9, 16, 25]

# ── Estilo comprensión: ──
cuadrados = [x ** 2 for x in range(1, 6)]
print(cuadrados)    # [1, 4, 9, 16, 25]

# La comprensión se lee como una frase en matemáticas:
# "la lista de x² para cada x en {1,2,3,4,5}"
# [x**2  for x  in range(1,6)]
#  ↑qué  ↑var  ↑de dónde
Bowl de verduras salteadas con quinoa junto a ingredientes crudos dispersos: tomates cherry, zanahoria, brócoli, limón y hierbas frescas sobre superficie blanca
Los ingredientes crudos (la lista original) se transforman en algo nuevo y elaborado (la lista resultado) siguiendo una receta uniforme. Eso es exactamente lo que hace una comprensión: toma cada elemento, le aplica la misma transformación, y devuelve la colección resultante. Fuente: Pexels (licencia libre).

📋 List comprehension: la forma más usada

La list comprehension crea una lista nueva. Su estructura es [expresion for variable in iterable]. Funciona con cualquier iterable: listas, strings, tuplas, rangos, diccionarios, generadores...

# Transformaciones básicas:
numeros   = [1, 2, 3, 4, 5]
cuadrados = [n ** 2 for n in numeros]         # [1, 4, 9, 16, 25]
dobles    = [n * 2  for n in numeros]         # [2, 4, 6, 8, 10]
strings   = [str(n) for n in numeros]         # ['1','2','3','4','5']

# Sobre strings:
palabras   = ["  Ana  ", "LUIS", "marta", "Pedro  "]
normalizadas = [p.strip().title() for p in palabras]
# ['Ana', 'Luis', 'Marta', 'Pedro']

# Extraer campo de lista de diccionarios:
usuarios = [
    {"nombre": "Ana",   "edad": 28, "activo": True},
    {"nombre": "Luis",  "edad": 34, "activo": False},
    {"nombre": "Marta", "edad": 22, "activo": True},
]
nombres = [u["nombre"] for u in usuarios]
# ['Ana', 'Luis', 'Marta']

edades = [u["edad"] for u in usuarios]
media_edad = sum(edades) / len(edades)        # 28.0

# Sobre range():
pares       = [x for x in range(20) if x % 2 == 0]   # filtro incluido
potencias_2 = [2 ** i for i in range(10)]
# [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

# Sobre caracteres de un string:
vocales = [c for c in "Pitágoras" if c.lower() in "aeiouáéíóú"]
# ['i', 'á', 'o', 'a']

# Sobre líneas de un archivo:
# with open("datos.txt") as f:
#     lineas = [linea.strip() for linea in f if linea.strip()]

🔍 Filtrar con if dentro de la comprensión

Añade if condicion al final para incluir solo los elementos que cumplan la condición. Los que no la cumplen simplemente no aparecen en la lista resultado.

numeros = list(range(-5, 6))   # [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]

positivos = [n for n in numeros if n > 0]      # [1, 2, 3, 4, 5]
negativos = [n for n in numeros if n < 0]      # [-5, -4, -3, -2, -1]
impares   = [n for n in numeros if n % 2 != 0] # [-5, -3, -1, 1, 3, 5]

# Múltiples condiciones (and implícito):
rango_medio = [n for n in numeros if n > -3 and n < 3]
# [-2, -1, 0, 1, 2]

# Filtrar usuarios activos y mayores de 25:
activos_senior = [
    u["nombre"]
    for u in usuarios
    if u["activo"] and u["edad"] > 25
]
# ['Ana']

# Filtrar None y vacíos:
datos = [42, None, 7, "", 15, None, "hola", 0]
validos = [d for d in datos if d is not None and d != "" and d != 0]
# [42, 7, 15, 'hola']

# Pythónico: usar truthiness directamente
# (cuidado: esto filtra también 0, False, "" y [])
no_falsy = [d for d in datos if d]
# [42, 7, 15, 'hola']
🎲 Dato inútil pero verdadero: La list comprehension fue introducida en Python 2.0 en el año 2000, inspirada directamente en la notación de conjuntos de las matemáticas (que a su vez tiene miles de años). Esto significa que cada vez que escribes [x**2 for x in nums if x > 0] estás usando una sintaxis que conceptualmente es más antigua que la imprenta. Los matemáticos medievales escribían exactamente la misma idea en pergamino con pluma de ganso. Solo que tardaban más y no tenían autocompletado.

🔀 Expresión ternaria: transformar con condición

El operador ternario A if condicion else B permite transformar cada elemento de una manera u otra dependiendo de la condición. Todos los elementos aparecen en el resultado, pero con valores diferentes.

# Diferencia clave:
# [f(x) if cond else g(x) for x in L]  → transforma TODOS (en A o en B)
# [f(x) for x in L if cond]            → FILTRA, solo los que cumplen

notas = [3.5, 7.2, 4.8, 9.1, 5.0, 2.3]

# Ternario: clasifica TODAS las notas
estados = ["aprobado" if n >= 5 else "suspenso" for n in notas]
# ['suspenso', 'aprobado', 'suspenso', 'aprobado', 'aprobado', 'suspenso']

# Filtro: devuelve SOLO las aprobadas
aprobadas = [n for n in notas if n >= 5]
# [7.2, 9.1, 5.0]

# Ternario con iconos:
iconos = ["✅" if n >= 5 else "❌" for n in notas]
# ['❌', '✅', '❌', '✅', '✅', '❌']

# Normalizar valores numéricos:
nums = [-3, 0, 5, -1, 8, 0, -4]
absolutos  = [abs(n)      for n in nums]          # valores absolutos
normalizados = [n if n >= 0 else 0 for n in nums] # clipear a 0 mínimo
# [0, 0, 5, 0, 8, 0, 0]

# Ternario anidado (úsalo con moderación — legibilidad primero):
puntuaciones = [100, 75, 50, 25, 0]
etiquetas = [
    "excelente" if p >= 90
    else "bien" if p >= 70
    else "regular" if p >= 50
    else "insuficiente"
    for p in puntuaciones
]
# ['excelente', 'bien', 'regular', 'insuficiente', 'insuficiente']

📖 Dict comprehension: construir diccionarios

La dict comprehension usa llaves con dos puntos: {clave: valor for ... in ...}. Perfecta para transformar, invertir o filtrar diccionarios existentes, o para construirlos a partir de dos listas.

# Básica: cuadrado de cada número
cuadrados = {n: n**2 for n in range(1, 6)}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Invertir un diccionario (clave ↔ valor):
paises_capitales = {"España": "Madrid", "Francia": "París", "Italia": "Roma"}
capitales_paises = {v: k for k, v in paises_capitales.items()}
# {'Madrid': 'España', 'París': 'Francia', 'Roma': 'Italia'}

# Filtrar un diccionario:
precios = {"café": 1.50, "tostada": 2.20, "zumo": 2.80, "agua": 1.00}
baratos  = {k: v for k, v in precios.items() if v < 2.0}
# {'café': 1.50, 'agua': 1.00}

# Construir desde dos listas con zip:
campos  = ["nombre", "edad", "ciudad"]
valores = ["Ana", 28, "Madrid"]
perfil  = {k: v for k, v in zip(campos, valores)}
# {'nombre': 'Ana', 'edad': 28, 'ciudad': 'Madrid'}
# Equivalente más corto: dict(zip(campos, valores))

# Normalizar claves (eliminar espacios, a minúsculas):
config_raw = {"  Debug ": True, "MAX_RETRIES": 3, " timeout": 30}
config = {k.strip().lower(): v for k, v in config_raw.items()}
# {'debug': True, 'max_retries': 3, 'timeout': 30}

# Contar frecuencia de letras:
texto = "comprension"
frecuencias = {c: texto.count(c) for c in set(texto)}
# {'c': 1, 'o': 2, 'm': 1, 'p': 1, 'r': 2, 'e': 1, 'n': 2, 'i': 1, 's': 1}

# Indexar lista de objetos por un campo:
usuarios_por_id = {u["nombre"]: u for u in usuarios}
# {'Ana': {...}, 'Luis': {...}, 'Marta': {...}}
ana = usuarios_por_id["Ana"]    # acceso O(1)
Bloques de tiza pastel de colores vivos —rojo, naranja, rosa, amarillo, verde, azul, morado— apilados en filas sobre superficie oscura
Cada bloque tiene un color (la clave) y una posición propia en la estructura (el valor). Construir un diccionario con una dict comprehension es exactamente esto: tomar elementos individuales con identidad propia y organizarlos en una estructura clave-valor. Fuente: Pexels (licencia libre).

🔵 Set comprehension: conjuntos únicos

La set comprehension usa llaves sin dos puntos: {expresion for ... in ...}. El resultado es un conjunto — sin duplicados y sin orden garantizado.

# Cuadrados únicos (elimina duplicados automáticamente):
nums = [-3, -2, -1, 0, 1, 2, 3]
cuadrados_unicos = {n**2 for n in nums}
# {0, 1, 4, 9}  (no {0, 1, 1, 4, 4, 9, 9})

# Letras únicas de un texto (sin espacios, en minúscula):
texto = "Comprensiones de Python"
letras = {c.lower() for c in texto if c.isalpha()}
# {'c', 'o', 'm', 'p', 'r', 'e', 'n', 's', 'i', 'd', 'y', 't', 'h'}

# Dominios únicos de una lista de emails:
emails = ["ana@gmail.com", "luis@yahoo.es", "marta@gmail.com", "pedro@outlook.com"]
dominios = {email.split("@")[1] for email in emails}
# {'gmail.com', 'yahoo.es', 'outlook.com'}

# Diferencia entre dos listas (elementos en a pero no en b):
lista_a = [1, 2, 3, 4, 5, 6]
lista_b = [4, 5, 6, 7, 8, 9]
solo_en_a = set(lista_a) - set(lista_b)      # {1, 2, 3}
solo_en_b = set(lista_b) - set(lista_a)      # {7, 8, 9}
comunes   = set(lista_a) & set(lista_b)      # {4, 5, 6}

# ⚠️ {} no es un set vacío — es un diccionario vacío:
tipo_llaves  = type({})      # 
tipo_set     = type(set())   #    ← set vacío correcto

# Set comprehension no vacía: sí funciona con {}
mi_set = {x for x in [1, 2, 2, 3]}    # {1, 2, 3}

⚡ Generator expressions: memoria eficiente

Los generadores usan paréntesis: (expresion for ... in ...). No crean la colección completa en memoria — generan cada valor cuando se necesita. Indispensables para conjuntos de datos grandes o cuando solo necesitas iterar una vez.

import sys

# Comparativa de memoria:
lista_comp = [x**2 for x in range(1_000_000)]
gen_expr   = (x**2 for x in range(1_000_000))

print(sys.getsizeof(lista_comp))   # ~8 MB
print(sys.getsizeof(gen_expr))     # ~120 bytes (constante)

# Usar con sum, max, min, any, all (sin crear lista intermedia):
numeros = range(1, 10_001)

total    = sum(x**2 for x in numeros)          # suma de cuadrados
maximo   = max(abs(x) for x in [-5, 3, -8, 2]) # 8
hay_neg  = any(x < 0 for x in [1, 2, -1, 4])   # True
todos_pos = all(x > 0 for x in [1, 2, 3, 4])   # True

# Longitud máxima de palabras en un texto:
oracion = "Las comprensiones de Python son elegantes y eficientes"
max_len = max(len(p) for p in oracion.split())   # 12 ("comprensiones")

# ⚠️ El generador se agota tras la primera iteración:
gen = (x**2 for x in range(5))
lista1 = list(gen)   # [0, 1, 4, 9, 16]
lista2 = list(gen)   # []  ← ya se agotó

# Si necesitas iterar varias veces: usa list comprehension
valores = [x**2 for x in range(5)]
print(sum(valores))   # puedes usarlo varias veces
print(max(valores))

# Pasar generador directamente a funciones que aceptan iterables:
palabras = ["ana", "luis", "marta", "pedro", "eva"]
# Sin lista intermedia:
resultado = ", ".join(p.title() for p in palabras if len(p) > 3)
# 'Luis, Marta, Pedro'

🔲 Comprensiones anidadas

Puedes incluir múltiples cláusulas for en una comprensión. El orden es el mismo que en bucles anidados: el primer for es el bucle exterior. Úsalo con moderación — con más de dos niveles, la legibilidad cae drásticamente.

# Aplanar una lista de listas (el caso más habitual):
matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
plana  = [v for fila in matriz for v in fila]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Equivalente con for anidados:
# plana = []
# for fila in matriz:
#     for v in fila:
#         plana.append(v)

# Todas las combinaciones de dos listas:
colores = ["rojo", "azul"]
tallas  = ["S", "M", "L"]
prendas = [f"{color}-{talla}" for color in colores for talla in tallas]
# ['rojo-S', 'rojo-M', 'rojo-L', 'azul-S', 'azul-M', 'azul-L']

# Matriz transpuesta:
matriz_3x2 = [[1, 2], [3, 4], [5, 6]]
transpuesta = [[fila[i] for fila in matriz_3x2] for i in range(2)]
# [[1, 3, 5], [2, 4, 6]]
# (o más limpio: list(zip(*matriz_3x2)))

# Aplanar con filtro:
datos = [[1, -2, 3], [-4, 5, -6], [7, 8, -9]]
positivos = [v for fila in datos for v in fila if v > 0]
# [1, 3, 5, 7, 8]

# ❌ Tres niveles: ya no es legible, mejor for explícito:
# [v for bloque in estructura for fila in bloque for v in fila]
# → demasiado para una sola línea
Anatomía y variantes de las comprensiones en Python: sintaxis general desglosada en colores con las cuatro variantes — list, dict, set y generator — con ejemplos de código para cada una
Las cuatro variantes: [ ] list (crea lista en memoria), { : } dict (crea diccionario), { } set (crea conjunto sin duplicados), ( ) generator (genera valores perezosamente). Infografía: Ciberaula.

🚫 Cuándo NO usar comprensiones

La comprensión es una herramienta poderosa, pero no siempre es la correcta. La legibilidad siempre tiene prioridad sobre la concisión.

# ❌ 1. Efectos secundarios: usa for, no comprehension
# Esto funciona pero es un antipatrón — la comprensión crea una lista
# desechada solo para los efectos secundarios:
[print(x) for x in numeros]       # ❌ antipatrón
for x in numeros: print(x)        # ✅ intención clara

# ❌ 2. Lógica compleja: usa for
# Difícil de leer de un vistazo:
resultado = [
    procesar_avanzado(item, contexto=True)
    if item.estado == "activo" and item.prioridad > 5
    else valor_por_defecto(item)
    for item in coleccion
    if item is not None and item.categoria in categorias_validas
]
# ✅ Mejor como for con comentarios

# ❌ 3. Más de dos for anidados:
# [v for a in b for c in d for v in e]  # imposible de leer
# ✅ Usa bucles explícitos o itertools.product

# ❌ 4. Reutilizar un generador (se agota):
gen = (x**2 for x in range(5))
total = sum(gen)
maximo = max(gen)    # ❌ gen ya está agotado → ValueError: max() arg is empty sequence
# ✅ Si necesitas reutilizar: convierte a lista
valores = [x**2 for x in range(5)]
total = sum(valores)
maximo = max(valores)    # ✅

# La pregunta clave antes de escribir una comprensión:
# ¿Una persona que no escribió este código lo entiende en 5 segundos?
# Si la respuesta es no → usa for con nombre de variable descriptivo.
Ficha de referencia de comprensiones en Python: comparativa for vs comprehension, anidadas, ternario, rendimiento y errores comunes incluyendo el set vacío y el generator agotado
Referencia rápida: for vs comprehension (con cuándo no usarla), anidadas con la regla del orden, ternario vs filtro, comparativa de rendimiento y los tres errores más comunes. Ficha de referencia: Ciberaula.

✅ Resumen y próximos pasos

Las comprensiones son la forma más Pythónica de construir listas, diccionarios y sets a partir de iterables. [expr for x in it if cond] para listas, {k: v for ...} para dicts, {expr for ...} para sets, y (expr for ...) para generadores eficientes en memoria. El ternario A if cond else B transforma todos los elementos; el filtro if cond al final solo incluye los que pasan. Y la regla de oro: si no se entiende en 5 segundos, usa un bucle for.

Con esto cerramos el Módulo 2 — Control de flujo completo: condicionales, for, while, break/continue/pass, y comprensiones. El siguiente módulo: Estructuras de datos — listas, tuplas, diccionarios, conjuntos y strings en profundidad.

❓ Preguntas frecuentes

❓ Preguntas frecuentes sobre Comprensiones en Python: list, dict, set y generator expressions

Las dudas más comunes respondidas de forma clara y directa.

Usa una comprensión cuando la operación es una transformación directa, un filtrado simple, o la combinación de ambas sobre un iterable, y el resultado cabe en una línea o dos legibles. Un bucle for es mejor cuando la lógica es compleja, tiene efectos secundarios (modificar variables externas, imprimir, escribir en disco), necesita manejo de excepciones interno, o requeriría más de dos líneas de comprensión para ser comprensible. La legibilidad siempre tiene prioridad sobre la concisión.
Los corchetes [] crean una list comprehension que evalúa toda la expresión inmediatamente y almacena todos los resultados en una lista en memoria. Los paréntesis () crean una generator expression que es perezosa: no evalúa nada hasta que alguien itera sobre el generador, y genera los valores uno a uno sin almacenarlos todos a la vez. El generador ocupa memoria constante (unos 120 bytes) independientemente del tamaño del iterable, mientras que la lista ocupa espacio proporcional al número de elementos.
Sí. Puedes usar como fuente cualquier objeto iterable: listas, tuplas, strings, diccionarios (con .items(), .keys() o .values()), sets, ficheros de texto, rangos, generadores, o cualquier objeto que implemente el protocolo de iteración. El resultado siempre es una lista nueva (en list comprehension), un dict nuevo, un set nuevo, o un generador, independientemente del tipo del iterable de entrada.
Sí, y el orden de lectura es el mismo: el for más externo de la comprensión corresponde al bucle más externo. [v for fila in matriz for v in fila] es equivalente a: for fila in matriz: for v in fila: resultado.append(v). La regla es leer los for de izquierda a derecha, igual que los leerías de afuera hacia adentro en bucles anidados. La limitación práctica es que con más de dos niveles de anidamiento la comprensión se vuelve difícil de leer, por lo que se recomienda usar bucles for explícitos o itertools.
Porque {} fue asignado a los diccionarios vacíos antes de que Python tuviera el tipo set en su sintaxis literal. Para crear un set vacío usa set(). Para una set comprehension con al menos un elemento, los corchetes rizados funcionan: {x for x in lista}. Pero una set comprehension vacía como {} es un diccionario, no un set. Este es uno de los pocos casos donde la sintaxis de Python tiene una irregularidad histórica.
No, en Python 3. Las variables de iteración (como x en [x for x in lista]) tienen su propio ámbito dentro de la comprensión y no contaminan el ámbito externo. Esto es diferente de Python 2, donde sí se filtraban. En Python 3, después de [x*2 for x in range(5)], la variable x no existe en el ámbito externo. Este cambio fue intencional para evitar efectos colaterales inesperados.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Comprensiones en Python: list, dict, set y generator expressions? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!