Diccionarios en Python
En esta lección
Un diccionario guarda datos en parejas de clave y valor. En lugar de acceder por posición, como en las listas, accedes por nombre. Es el equivalente a los objetos de JavaScript o los mapas de Java, y una de las estructuras que más usarás en Python. En esta lección aprenderás a crear diccionarios, leerlos sin errores, modificarlos, recorrerlos, anidarlos y usarlos para contar cosas.
Crear un diccionario
Las parejas clave: valor van entre llaves y separadas por comas:
alumno = {
"nombre": "Marta",
"edad": 21,
"curso": "Python",
}
print(alumno)
# {'nombre': 'Marta', 'edad': 21, 'curso': 'Python'}
- Las claves suelen ser textos, pero pueden ser números, tuplas o cualquier valor inmutable.
- Los valores pueden ser cualquier cosa: números, textos, listas u otros diccionarios.
- Las claves no se repiten: si escribes la misma dos veces, gana la última.
- Desde Python 3.7, los diccionarios conservan el orden de inserción.
Otras formas de crear diccionarios:
vacio = {}
config = dict(idioma="es", tema="oscuro")
print(config)
# {'idioma': 'es', 'tema': 'oscuro'}
Leer valores
print(alumno["nombre"]) # Marta
print(alumno["email"]) # KeyError: 'email'
Si la clave no existe, los corchetes lanzan KeyError. Cuando no estás seguro de que la clave exista, usa get: devuelve None, o el valor por defecto que le digas, en lugar de dar error.
print(alumno.get("email")) # None
print(alumno.get("email", "sin email")) # sin email
print(alumno.get("edad", 0)) # 21 (existe, así que devuelve su valor)
¿Cuándo usar cada uno? Corchetes cuando la clave debe existir (si falta, es un error del programa y conviene enterarse); get cuando es normal que falte.
Comprobar si existe una clave: in
print("edad" in alumno) # True
print("Marta" in alumno) # False: in mira las claves, no los valores
print("Marta" in alumno.values()) # True
Añadir, cambiar y borrar
alumno["edad"] = 22 # cambia un valor existente
alumno["email"] = "m@ej.com" # si la clave no existe, la crea
del alumno["curso"] # borra una clave
print(alumno)
print(len(alumno)) # número de parejas
# {'nombre': 'Marta', 'edad': 22, 'email': 'm@ej.com'}
# 3
pop: borrar y recuperar el valor
pop quita una clave y devuelve su valor. Admite un valor por defecto para no fallar si la clave no está:
carrito = {"pan": 2, "leche": 1, "huevos": 12}
unidades = carrito.pop("leche")
print(unidades) # 1
print(carrito) # {'pan': 2, 'huevos': 12}
print(carrito.pop("azucar", 0)) # 0 (no existía)
del y pop sin valor por defecto lanzan KeyError si la clave no existe.
update: modificar varias claves a la vez
perfil = {"nombre": "Luis", "ciudad": "Bilbao"}
perfil.update({"ciudad": "Madrid", "edad": 30})
print(perfil)
# {'nombre': 'Luis', 'ciudad': 'Madrid', 'edad': 30}
Las claves que ya existen se sobrescriben y las nuevas se añaden.
setdefault: leer o crear si no existe
setdefault(clave, valor) devuelve el valor de la clave si existe; si no, la crea con ese valor y lo devuelve. Es muy útil para agrupar:
alumnos = [("Ana", "DAW"), ("Luis", "DAM"), ("Eva", "DAW")]
por_ciclo = {}
for nombre, ciclo in alumnos:
por_ciclo.setdefault(ciclo, []).append(nombre)
print(por_ciclo)
# {'DAW': ['Ana', 'Eva'], 'DAM': ['Luis']}
La primera vez que aparece "DAW", setdefault crea una lista vacía; las siguientes, devuelve la que ya existe, y en ambos casos se añade el nombre.
Recorrer un diccionario
precios = {"café": 1.5, "tostada": 2.2, "zumo": 2.8}
for producto in precios: # recorre las claves
print(producto)
# café
# tostada
# zumo
for producto, precio in precios.items(): # claves y valores a la vez
print(f"{producto}: {precio:.2f} €")
# café: 1.50 €
# tostada: 2.20 €
# zumo: 2.80 €
Tres métodos devuelven “vistas” del diccionario:
| Método | Devuelve | Ejemplo de uso |
|---|---|---|
keys() | Las claves | list(precios.keys()) |
values() | Los valores | sum(precios.values()) da 6.5 |
items() | Parejas (clave, valor) | for k, v in precios.items(): |
Cuidado: no añadas ni borres claves mientras recorres el diccionario: obtendrás
RuntimeError: dictionary changed size during iteration. Recorre una copia, por ejemplofor clave in list(precios):.
Recorrer en orden
Un diccionario conserva el orden de inserción, pero a menudo quieres otro. sorted te da las claves ordenadas:
for producto in sorted(precios):
print(producto, precios[producto])
# café 1.5
# tostada 2.2
# zumo 2.8
Para ordenar por valor, pasa a sorted una key que devuelva el valor:
for producto in sorted(precios, key=precios.get, reverse=True):
print(producto, precios[producto])
# zumo 2.8
# tostada 2.2
# café 1.5
precios.get es la función que, dada una clave, devuelve su precio. Para criterios más complejos se usan lambda, que verás en Funciones avanzadas.
Contar cosas con un diccionario
Un uso clásico: contar cuántas veces aparece cada elemento.
votos = ["gato", "perro", "gato", "pez", "gato", "perro"]
recuento = {}
for voto in votos:
recuento[voto] = recuento.get(voto, 0) + 1
print(recuento)
# {'gato': 3, 'perro': 2, 'pez': 1}
recuento.get(voto, 0) devuelve cuántos votos lleva ese animal, o 0 si todavía no tiene ninguno.
collections.Counter
Contar es tan habitual que Python trae una herramienta hecha: Counter, un diccionario especializado en contar.
from collections import Counter
recuento = Counter(votos)
print(recuento)
print(recuento["gato"]) # 3
print(recuento["loro"]) # 0: no da KeyError
print(recuento.most_common(2)) # los 2 más votados
# Counter({'gato': 3, 'perro': 2, 'pez': 1})
# 3
# 0
# [('gato', 3), ('perro', 2)]
Funciona con cualquier secuencia: Counter("banana") cuenta letras. Hacer el recuento a mano es buen ejercicio para entender los diccionarios; en código real, usa Counter.
Fusionar diccionarios con |
Desde Python 3.9, el operador | une dos diccionarios en uno nuevo. Si una clave está en los dos, gana el de la derecha:
por_defecto = {"idioma": "es", "tema": "claro", "avisos": True}
del_usuario = {"tema": "oscuro"}
config = por_defecto | del_usuario
print(config)
# {'idioma': 'es', 'tema': 'oscuro', 'avisos': True}
Es el patrón típico de “valores por defecto que el usuario puede sobrescribir”. a |= b modifica a directamente, igual que a.update(b).
Qué puede ser clave
Las claves tienen que ser inmutables (técnicamente, hashables): str, int, float, bool, tuple… Una lista no puede serlo:
distancias = {("Madrid", "Sevilla"): 530} # una tupla como clave: bien
print(distancias[("Madrid", "Sevilla")])
# 530
errores = {["a", "b"]: 1}
# TypeError: unhashable type: 'list'
¿Por qué? El diccionario encuentra los valores tan rápido porque calcula un número a partir de la clave (su hash). Si la clave pudiera cambiar, ese número dejaría de ser válido. Tienes más sobre tuplas como claves en Tuplas y conjuntos.
Diccionarios anidados
Un valor puede ser otro diccionario (o una lista). Así se representan datos con estructura:
tienda = {
"nombre": "Librería Sol",
"direccion": {"calle": "Mayor 5", "ciudad": "Burgos"},
"libros": ["El Quijote", "Rayuela"],
}
print(tienda["direccion"]["ciudad"]) # Burgos
print(tienda["libros"][0]) # El Quijote
tienda["direccion"]["cp"] = "09001" # añadir dentro del anidado
print(tienda["direccion"])
# {'calle': 'Mayor 5', 'ciudad': 'Burgos', 'cp': '09001'}
Para leer con seguridad en varios niveles, puedes encadenar get con un diccionario vacío por defecto: tienda.get("contacto", {}).get("telefono") devuelve None en lugar de fallar.
Listas de diccionarios
La forma más habitual de guardar datos en un programa: una lista de fichas.
alumnos = [
{"nombre": "Ana", "nota": 8},
{"nombre": "Luis", "nota": 4},
{"nombre": "Eva", "nota": 6.5},
]
for a in alumnos:
estado = "aprobado" if a["nota"] >= 5 else "suspenso"
print(f"{a['nombre']}: {estado}")
# Ana: aprobado
# Luis: suspenso
# Eva: aprobado
aprobados = [a["nombre"] for a in alumnos if a["nota"] >= 5]
print(aprobados)
# ['Ana', 'Eva']
Fíjate en las comillas de a['nombre'] dentro de la f-string: como la f-string usa comillas dobles, dentro se usan simples.
La última línea es una comprensión de lista. También existen las comprensiones de diccionario, que crean un diccionario en una línea, como {a["nombre"]: a["nota"] for a in alumnos}. Las verás en Comprensiones.
Diccionarios y JSON
Los diccionarios tienen la misma forma que el formato JSON, que es como se intercambian datos en la web:
import json
texto = json.dumps(alumnos[0]) # diccionario → texto JSON
print(texto)
de_vuelta = json.loads(texto) # texto JSON → diccionario
print(de_vuelta["nombre"])
# {"nombre": "Ana", "nota": 8}
# Ana
Guardar y leer JSON en ficheros lo verás en Ficheros y JSON.
Errores frecuentes
KeyErroral leer una clave que no existe: usageto comprueba conin.- Creer que
inbusca en los valores: busca en las claves. Para valores,in dic.values(). - Usar una lista como clave:
TypeError: unhashable type. Usa una tupla. - Modificar el diccionario mientras lo recorres:
RuntimeError. Recorrelist(dic). - Esperar que
{}sea un conjunto vacío: es un diccionario vacío. - Mezclar comillas en f-strings en versiones anteriores a 3.12:
f"{a["nombre"]}"falla; usaf"{a['nombre']}".
Resumen
| Tarea | Cómo |
|---|---|
| Crear | {"clave": valor}, dict(clave=valor), {} |
| Leer | d["clave"] (error si falta), d.get("clave", defecto) |
| Existe | "clave" in d |
| Añadir / cambiar | d["clave"] = valor, d.update(otro) |
| Borrar | del d["clave"], d.pop("clave", defecto) |
| Leer o crear | d.setdefault("clave", valor) |
| Recorrer | for k in d, for k, v in d.items() |
| Ordenar | sorted(d), sorted(d, key=d.get) |
| Contar | d.get(x, 0) + 1 o Counter(lista) |
| Fusionar | a | b |
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.