Comprensiones en Python: list, dict, set y generator expressions
- Qué es una comprensión y por qué usarla
- List comprehension: la forma más usada
- Filtrar con if dentro de la comprensión
- Expresión ternaria: transformar con condición
- Dict comprehension: construir diccionarios
- Set comprehension: conjuntos únicos
- Generator expressions: memoria eficiente
- Comprensiones anidadas
- Cuándo NO usar comprensiones
- Preguntas frecuentes
[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
📋 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']
[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)
🔵 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
🚫 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.
✅ 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.
💬 Foro de discusión
¿Tienes dudas sobre Comprensiones en Python: list, dict, set y generator expressions? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!