Módulo 1 · Lección 28 de 59
Diccionarios: buscar por nombre
Guardar datos emparejados con una etiqueta y acceder a ellos por esa etiqueta en vez de por su posición.
En la lección de ordenar listas quedó un fallo a la vista: para guardar jugadores y sus puntos usábamos dos listas paralelas y las cruzábamos con index(), y en cuanto dos jugadores empataban, el informe mentía. El problema de fondo es que una lista solo sabe de posiciones, y la posición 2 no significa nada; recordar que el nombre de la posición 2 va con los puntos de la posición 2 es trabajo tuyo, y tarde o temprano se rompe.
Un diccionario guarda pares: una clave y su valor. En vez de preguntar "¿qué hay en la posición 2?", preguntas "¿cuántos puntos tiene Luis?". Es la estructura de datos más útil de Python, y una vez la tengas no habrá programa en el que no aparezca.
Crear un diccionario#
Se escribe con llaves, y dentro pares clave: valor separados por comas:
puntos = {"Marta": 3400, "Luis": 2100, "Ana": 1200}
print(puntos)
print(type(puntos))
print(len(puntos)){'Marta': 3400, 'Luis': 2100, 'Ana': 1200}
<class 'dict'>
3len() cuenta pares, no claves ni valores por separado.
Cuando hay más de tres o cuatro pares, se escribe uno por línea, que es como lo verás casi siempre:
usuario = {
"nombre": "Erwin",
"edad": 34,
"ciudad": "Madrid",
"activo": True,
}
print(usuario["nombre"])
print(usuario["edad"])Erwin
34Fíjate en ese segundo uso, que es distinto del primero. En puntos todas las claves eran nombres de personas y todos los valores puntuaciones: el diccionario servía como tabla de búsqueda. En usuario cada clave es un campo distinto: el diccionario sirve como registro que describe una cosa. Los dos usos son correctos y los dos son frecuentísimos.
El diccionario vacío se escribe {}, y también con dict():
inventario = {}
print(inventario, len(inventario), bool(inventario)){} 0 FalseIgual que las listas y los conjuntos, un diccionario vacío es falso, así que if inventario: pregunta si tiene algo dentro.
Acceder a un valor#
Con corchetes, poniendo la clave donde en una lista pondrías el índice:
capitales = {
"España": "Madrid",
"Francia": "París",
"Italia": "Roma",
}
print(capitales["Francia"])
print(capitales["Italia"].upper())París
ROMALa clave no tiene nada que ver con el orden: capitales["Francia"] funciona igual esté donde esté. Y a diferencia de la búsqueda en una lista, el acceso es inmediato aunque el diccionario tenga un millón de entradas, por el mismo motivo que hacía rápidos a los conjuntos.
in comprueba las claves#
El operador in sobre un diccionario mira las claves, no los valores. Es lo más importante que hay que recordar de esta lección después del KeyError:
capitales = {"España": "Madrid", "Francia": "París"}
print("España" in capitales)
print("Madrid" in capitales) # es un valor, no una clave
print("Madrid" in capitales.values()) # así síTrue
False
TruePuede parecer arbitrario, pero es lo coherente: la clave es la forma de encontrar las cosas, así que preguntar "¿está en el diccionario?" significa "¿tengo una entrada con ese nombre?".
Con eso, el acceso seguro se escribe así:
capitales = {"España": "Madrid", "Francia": "París"}
pais = "Portugal"
if pais in capitales:
print(f"La capital de {pais} es {capitales[pais]}")
else:
print(f"No tengo la capital de {pais}")No tengo la capital de Portugalget(): acceder sin miedo#
get() hace lo mismo que los corchetes, pero cuando la clave no existe devuelve None en vez de fallar:
capitales = {"España": "Madrid", "Francia": "París"}
print(capitales.get("España"))
print(capitales.get("Portugal"))Madrid
NoneY acepta un segundo argumento con el valor que quieres recibir cuando no hay nada:
capitales = {"España": "Madrid", "Francia": "París"}
print(capitales.get("Portugal", "desconocida"))
print(capitales.get("España", "desconocida"))desconocida
MadridEse valor por defecto es lo que convierte a get() en la opción preferente. Compara las dos formas de contar algo que puede no estar:
existencias = {"teclado": 4, "ratón": 0}
# con corchetes hay que protegerse
if "monitor" in existencias:
cantidad = existencias["monitor"]
else:
cantidad = 0
# con get sale en una línea
cantidad = existencias.get("monitor", 0)
print(f"Monitores: {cantidad}")
print(f"Teclados: {existencias.get('teclado', 0)}")Monitores: 0
Teclados: 4Cinco líneas convertidas en una, y sin repetir el nombre de la clave dos veces.
Qué puede ser una clave#
Una clave tiene que ser inmutable, exactamente igual que los elementos de un conjunto y por el mismo motivo: el diccionario calcula una huella de la clave para saber dónde guardarla, y si la clave cambiara por dentro, esa huella dejaría de encontrarla.
Valen cadenas, números, booleanos y tuplas:
mezcla = {
"texto": "las cadenas son las claves más habituales",
42: "los enteros también valen",
3.14: "y los decimales, aunque se usan poco",
True: "y los booleanos",
("Madrid", "Sevilla"): "y las tuplas, para claves compuestas",
}
print(mezcla[42])
print(mezcla[("Madrid", "Sevilla")])los enteros también valen
y las tuplas, para claves compuestasNo valen listas, diccionarios ni conjuntos:
distancias = {["Madrid", "Sevilla"]: 530}TypeError: cannot use 'list' as a dict key (unhashable type: 'list')Los valores, en cambio, pueden ser cualquier cosa, sin restricción: números, textos, listas, otros diccionarios. Esa asimetría es la que da tanto juego a los diccionarios, y la explotaremos en la lección de estructuras anidadas.
receta = {
"nombre": "Tortilla de patatas",
"raciones": 4,
"ingredientes": ["patatas", "huevos", "cebolla", "aceite"],
"vegetariana": True,
}
print(receta["ingredientes"])
print(receta["ingredientes"][0])
print(len(receta["ingredientes"]))['patatas', 'huevos', 'cebolla', 'aceite']
patatas
4Fíjate en el doble corchete de la segunda línea: primero se busca la clave "ingredientes", que devuelve una lista, y después se pide el elemento 0 de esa lista. Se lee de izquierda a derecha, un paso cada vez.
Comparación con las otras colecciones#
| Lista | Conjunto | Diccionario | |
|---|---|---|---|
| Se escribe | [1, 2] | {1, 2} | {"a": 1} |
| Vacío | [] | set() | {} |
| Se accede por | Índice | No se accede | Clave |
in mira | Los elementos | Los elementos | Las claves |
Velocidad de in | Lenta | Muy rápida | Muy rápida |
| Duplicados | Sí | No | No en las claves |
| Para qué | Datos en orden | Pertenencia y únicos | Datos con nombre |
La forma corta de decidir: si tus datos tienen una etiqueta natural (un nombre, un código, un identificador) y vas a buscar por ella, es un diccionario.
Un ejemplo completo#
telefonos = {
"Ana": "600 111 222",
"Luis": "610 333 444",
"Marta": "620 555 666",
}
print(f"Contactos en la agenda: {len(telefonos)}")
print(f"Teléfono de Luis: {telefonos['Luis']}")
buscado = "Pedro"
print(f"Teléfono de {buscado}: {telefonos.get(buscado, 'no está en la agenda')}")
if buscado in telefonos:
print(f"{buscado} está guardado")
else:
print(f"{buscado} no está guardado")
# El valor puede ser una lista: varios teléfonos por contacto
telefonos_multiples = {
"Ana": ["600 111 222", "910 000 111"],
"Luis": ["610 333 444"],
}
print(f"Ana tiene {len(telefonos_multiples['Ana'])} teléfonos")
print(f"El primero es {telefonos_multiples['Ana'][0]}")Contactos en la agenda: 3
Teléfono de Luis: 610 333 444
Teléfono de Pedro: no está en la agenda
Pedro no está guardado
Ana tiene 2 teléfonos
El primero es 600 111 222