Comprensiones de Diccionarios y Conjuntos en Python
Domina las comprensiones de diccionarios, conjuntos y expresiones generadoras en Python con sintaxis clara, ejemplos reales y errores comunes.
Las comprensiones de diccionarios, las comprensiones de conjuntos y las expresiones generadoras extienden la sintaxis compacta de la comprensión de listas a otras estructuras de datos. Este capítulo explica cada forma en profundidad: sintaxis, filtrado, anidamiento, patrones del mundo real y cuándo evitarlos.
Comprensiones de Diccionarios
Una comprensión de diccionario construye un nuevo dict a partir de cualquier iterable en una sola expresión. En lugar de escribir un bucle for que llama a d[key] = value en cada iteración, describes el mapeo de forma concisa dentro de llaves.
Sintaxis
new_dict = {key_expr: value_expr for item in iterable}Añade un filtro if opcional después del iterable:
new_dict = {key_expr: value_expr for item in iterable if condition}| Parte | Función |
|---|---|
key_expr | Expresión que produce cada clave |
value_expr | Expresión que produce cada valor |
item | Variable de bucle — toma cada valor del iterable en orden |
if condition | Filtro opcional — omite los elementos donde condition es False |
Ejemplo Básico: Construir un Mapa de Cuadrados
squares = {x: x ** 2 for x in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}El bucle for equivalente:
squares = {}
for x in range(1, 6):
squares[x] = x ** 2Ambos producen el mismo resultado; la forma de comprensión es más concisa y expresa la intención a simple vista.
Filtrar Entradas
Conserva solo los pares que satisfacen una condición:
prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80, "grape": 2.50}
expensive = {item: price for item, price in prices.items() if price >= 1.00}
print(expensive)
# {'apple': 1.2, 'grape': 2.5}Transformar Valores
Aplica una función o expresión aritmética a cada valor:
prices = {"apple": 1.20, "banana": 0.50, "orange": 0.80}
# Apply a 10% discount and round to 2 decimal places
discounted = {item: round(price * 0.9, 2) for item, price in prices.items()}
print(discounted)
# {'apple': 1.08, 'banana': 0.45, 'orange': 0.72}Intercambiar Claves y Valores
Invierte un diccionario (funciona correctamente cuando todos los valores son únicos):
capitals = {"France": "Paris", "Germany": "Berlin", "Japan": "Tokyo"}
inverted = {city: country for country, city in capitals.items()}
print(inverted)
# {'Paris': 'France', 'Berlin': 'Germany', 'Tokyo': 'Japan'}Si los valores no son únicos, las entradas posteriores sobrescriben a las anteriores. Usa esto solo cuando estés seguro de que los valores forman una correspondencia uno a uno.
Construir desde Dos Iterables con zip()
zip() empareja elementos de dos secuencias, lo que facilita la construcción de un diccionario a partir de listas separadas de claves y valores:
keys = ["name", "age", "city"]
values = ["Alice", 30, "Berlin"]
profile = {k: v for k, v in zip(keys, values)}
print(profile)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}Esto es equivalente a dict(zip(keys, values)), pero la forma de comprensión permite añadir fácilmente un filtro o transformar valores al mismo tiempo.
Normalizar Claves
Una tarea habitual en el mundo real es limpiar las claves de un diccionario; por ejemplo, convertir todas las claves a minúsculas al combinar datos de diferentes fuentes:
raw = {"Name": "Alice", "AGE": 30, "City": "Berlin"}
normalised = {k.lower(): v for k, v in raw.items()}
print(normalised)
# {'name': 'Alice', 'age': 30, 'city': 'Berlin'}Expresión de Valor Condicional (Ternario en el Valor)
Puedes usar una expresión ternaria en la posición del valor para elegir entre dos valores por elemento:
scores = {"Alice": 95, "Bob": 62, "Carol": 78, "Dave": 45}
grades = {name: "pass" if score >= 70 else "fail" for name, score in scores.items()}
print(grades)
# {'Alice': 'pass', 'Bob': 'fail', 'Carol': 'pass', 'Dave': 'fail'}Comprensiones de Diccionarios Anidadas
Puedes anidar una comprensión para manejar datos de varios niveles. Por ejemplo, extraer un campo de una lista de diccionarios anidados:
students = [
{"name": "Alice", "score": 95, "grade": "A"},
{"name": "Bob", "score": 82, "grade": "B"},
{"name": "Carol", "score": 91, "grade": "A"},
]
# Build a {name: score} mapping from the list
name_to_score = {s["name"]: s["score"] for s in students}
print(name_to_score)
# {'Alice': 95, 'Bob': 82, 'Carol': 91}Mantén el anidamiento superficial. Si la lógica se vuelve difícil de seguir, extrae una función auxiliar o usa un bucle for simple.
Comprensiones de Conjuntos
Una comprensión de conjunto construye un set en una sola expresión. Como los conjuntos solo contienen elementos únicos, los duplicados se eliminan automáticamente.
Sintaxis
new_set = {expression for item in iterable}
new_set = {expression for item in iterable if condition}La única diferencia visual respecto a una comprensión de diccionario es la ausencia de dos puntos: hay una sola expresión, no un par key: value.
Ejemplo Básico: Cuadrados Únicos
numbers = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {x ** 2 for x in numbers}
print(unique_squares)
# {1, 4, 9, 16}El orden de salida no está garantizado: los conjuntos no están ordenados, así que no dependas de un orden de presentación específico.
Deduplicar Mientras se Transforma
Un patrón habitual es normalizar cadenas y deduplicar en un solo paso:
tags = ["Python", "python", "PYTHON", "Data", "data", "DATA"]
unique_tags = {tag.lower() for tag in tags}
print(unique_tags)
# {'python', 'data'}Filtrar con una Condición
words = ["cat", "elephant", "dog", "rhinoceros", "ant"]
long_words = {w for w in words if len(w) > 4}
print(long_words)
# {'elephant', 'rhinoceros'}Encontrar Elementos Comunes
Las comprensiones de conjuntos se combinan de forma natural con las operaciones de conjuntos:
list_a = [1, 2, 3, 4, 5, 5, 6]
list_b = [4, 5, 6, 7, 8]
set_a = {x for x in list_a}
set_b = {x for x in list_b}
common = set_a & set_b
print(common)
# {4, 5, 6}Para una conversión directa sin transformación, set(list_a) es más sencillo. Usa una comprensión de conjunto cuando necesites filtrar o transformar durante la conversión.
Expresiones Generadoras
Una expresión generadora se parece a una comprensión de listas pero usa paréntesis en lugar de corchetes. En vez de construir toda la colección en memoria de una vez, produce un valor a la vez, bajo demanda (de forma perezosa).
Sintaxis
gen = (expression for item in iterable)
gen = (expression for item in iterable if condition)¿Por Qué Usar una Expresión Generadora?
| Escenario | Comprensión de lista | Expresión generadora |
|---|---|---|
| Necesitas iterar el resultado varias veces | Sí | No — los generadores se agotan tras un solo recorrido |
Necesitas acceso aleatorio por índice (result[3]) | Sí | No |
El resultado se pasa a sum(), max(), any(), etc. | Funciona pero reserva una lista | Preferida — transmite valores sin construir una lista |
| Secuencia muy grande o infinita | Puede quedarse sin memoria | Eficiente — un valor a la vez |
Ejemplo Básico
# List comprehension — builds the entire list in memory
squares_list = [x ** 2 for x in range(1, 6)]
print(squares_list) # [1, 4, 9, 16, 25]
# Generator expression — yields values one at a time
squares_gen = (x ** 2 for x in range(1, 6))
print(squares_gen) # <generator object <genexpr> at 0x...>
print(list(squares_gen)) # [1, 4, 9, 16, 25]El objeto generador en sí no es la lista: lo consumes iterando sobre él o pasándolo a una función.
Pasar Directamente a Funciones Integradas
Cuando pasas una expresión generadora como único argumento a una función, puedes omitir el par de paréntesis adicional:
total = sum(x ** 2 for x in range(1, 1001))
print(total) # 333833500
maximum = max(len(word) for word in ["apple", "banana", "kiwi"])
print(maximum) # 6
any_negative = any(x < 0 for x in [1, -2, 3])
print(any_negative) # TrueVentaja de Memoria
Para datos grandes, una expresión generadora evita cargar todo en RAM:
# Simulate a large log file as a list of strings
log_lines = [f"ERROR line {i}" if i % 100 == 0 else f"INFO line {i}" for i in range(1_000_000)]
# Generator scans lines without building an intermediate list
error_count = sum(1 for line in log_lines if line.startswith("ERROR"))
print(error_count) # 10000Los Generadores se Agotan Tras un Solo Recorrido
Este es el error más común:
gen = (x * 2 for x in range(5))
print(list(gen)) # [0, 2, 4, 6, 8]
print(list(gen)) # [] — the generator is now emptySi necesitas iterar el resultado más de una vez, usa una comprensión de lista o vuelve a crear el generador.
Encadenar Expresiones Generadoras
Las expresiones generadoras se pueden componer sin crear listas intermedias. Cada etapa extrae valores de la anterior:
numbers = range(1, 11)
# Stage 1: filter even numbers
evens = (x for x in numbers if x % 2 == 0)
# Stage 2: square them
even_squares = (x ** 2 for x in evens)
print(list(even_squares)) # [4, 16, 36, 64, 100]No se crea ninguna lista intermedia: los valores fluyen a través del pipeline de uno en uno.
Elegir la Forma Correcta
| Necesitas… | Usa |
|---|---|
| Una lista de valores transformados o filtrados | [expr for item in it] |
| Un diccionario a partir de pares de valores | {k: v for item in it} |
| Una colección sin duplicados | {expr for item in it} |
| Iteración de un solo recorrido eficiente en memoria | (expr for item in it) |
| Lógica compleja de múltiples instrucciones por elemento | Bucle for simple |
Errores Comunes
Comprensión de diccionario vs. comprensión de conjunto. Ambas usan llaves {}. La diferencia está en si escribes key: value (dict) o una sola expression (set). Un {} vacío siempre crea un dict vacío, no un conjunto vacío: usa set() para un conjunto vacío.
d = {} # empty dict
s = set() # empty set — NOT {}Claves sobrescritas. Si la expresión de clave produce duplicados, los valores posteriores sobrescriben silenciosamente a los anteriores:
data = [("a", 1), ("b", 2), ("a", 99)]
d = {k: v for k, v in data}
print(d) # {'a': 99, 'b': 2} — first 'a' is goneÁmbito de variables. En Python 3, la variable de bucle en cualquier comprensión es local a la comprensión y no se filtra al ámbito circundante:
x = "original"
result = {x: x.upper() for x in ["a", "b", "c"]}
print(x) # 'original' — comprehension's x did not overwrite thisLímite de legibilidad. Si necesitas más de una condición if o la expresión es larga, un bucle for simple con nombres de variables descriptivos suele ser más claro:
# Hard to read
result = {k: v for k, v in data.items() if k.startswith("user_") if v is not None}
# Easier to read
result = {}
for k, v in data.items():
if k.startswith("user_") and v is not None:
result[k] = vTemas Relacionados
- Comprensión de Listas — la base: sintaxis, filtrado, bucles anidados y cuándo usar un bucle
forsimple - Diccionarios de Python — fundamentos de diccionarios, creación y acceso
- Conjuntos de Python — creación de conjuntos, operaciones y casos de uso
- Iterar Diccionarios — todas las técnicas para iterar sobre diccionarios
- Iteradores de Python — cómo funciona el protocolo iterador de Python bajo el capó