Diccionarios — pares de clave y valor
Guardar y buscar valores por nombre, evitar KeyError con .get(), añadir y borrar claves, recorrer con .items(), y por qué una lista no puede ser una clave.
¿Cómo quieres aprender?
El problema que vamos a resolver
Hay que guardar las notas de tres personas —con qué nota es de quién—.
Las listas ofrecen solo una forma: dos listas, una al lado de la otra.
names = ["rafi", "ahmed", "bilal"]
marks = [72, 45, 90]
print(names[1], marks[1])ahmed 45Funciona. Y toda la estructura depende de tu disciplina, sin ayuda alguna de Python.
¿Añades a alguien a names y te olvidas de marks? Ahora las dos listas tienen longitudes distintas, y a partir de ahí names[2] y marks[2] describen a dos personas diferentes. Python no dirá ni palabra. ¿Ordenas names? La relación se rompe por completo, y en silencio.
Aquí está la raíz del asunto: la relación entre las dos cosas no está escrita en ninguna parte. La relación es solo "el mismo índice" — una coincidencia que puede romperse en cualquier momento.
Un diccionario escribe la relación en el código. Nombres en lugar de índices, y el par guardado junto en un solo sitio.
Al terminar este capítulo podrás
- Crear un diccionario y buscar un valor por su clave
- Evitar un
KeyErrorcon.get(), y saber cuándo conviene cada uno - Añadir, cambiar y borrar claves
- Recorrer un diccionario con
.keys(),.values()y.items() - Explicar por qué una lista no puede ser una clave pero una tupla sí
Requisitos previos: Tuplas y desempaquetado.
Un diccionario — pares de clave y valor
Llaves, con pares key: value dentro:
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}
print(marks)
print(marks["ahmed"])
print(len(marks)){'rafi': 72, 'ahmed': 45, 'bilal': 90}
45
3marks["ahmed"] — un nombre, no un índice. Y el nombre está ahí mismo en el código, así que seis meses después sigue diciendo qué se está pidiendo.
len() cuenta los pares: tres.
Una palabra sobre el orden. Desde Python 3.7 un diccionario recuerda el orden en que se metieron las cosas, y las imprime en ese orden. Pero un diccionario sirve para buscar cosas por nombre, no por posición — escribemarks[0]y Python buscará una clave que sea el número cero, no encontrará ninguna y se detendrá conKeyError: 0.
Una clave que no está — KeyError y .get()
marks = {"rafi": 72}
print(marks["dia"])KeyError: 'dia'Un KeyError significa "no hay ninguna clave con ese nombre". El mismo error en una lista dio un IndexError — otro nombre para el mismo tipo de error.
Para obtener una respuesta segura en vez de que Python se detenga, usa .get():
marks = {"rafi": 72}
print(marks.get("dia"))
print(marks.get("dia", 0))
print(marks.get("rafi", 0))None
0
72El segundo argumento es "dame esto si falta". Sin él recibes None.
¿Cuál, cuándo? Si la clave debería estar ahí, escribe marks["rafi"] — su ausencia es un bug, y el KeyError te lo entrega al instante. Si que falte la clave es normal, usa .get(). Usar .get() en todas partes deja que los datos malos pasen en silencio, y los datos malos es justo lo que estabas intentando encontrar.
Añadir, cambiar, borrar
marks = {"rafi": 72}
marks["dia"] = 88
marks["rafi"] = 75
print(marks)
del marks["rafi"]
print(marks){'rafi': 75, 'dia': 88}
{'dia': 88}La misma línea hace dos trabajos: si la clave falta, se añade; si existe, se reemplaza. No hay un método aparte de "añadir".
Y de ahí sale una regla importante — una clave solo puede aparecer una vez:
marks = {"rafi": 72, "rafi": 90}
print(marks){'rafi': 90}Sin error. La segunda borró la primera en silencio. Esto pasa rutinariamente al cargar datos de un archivo, y es una forma común de que se pierdan registros.
in busca las claves, no los valores
marks = {"rafi": 72, "ahmed": 45}
print("rafi" in marks)
print(72 in marks)True
False72 está ahí sin duda como valor, pero in busca solo entre las claves. Para buscar entre los valores tienes que decirlo: 72 in marks.values().
Recorrerlo, de tres formas
marks = {"rafi": 72, "ahmed": 45}
for name in marks:
print(name)
print("---")
for name, mark in marks.items():
print(name, mark)
print("---")
print(list(marks.keys()))
print(list(marks.values()))rafi
ahmed
---
rafi 72
ahmed 45
---
['rafi', 'ahmed']
[72, 45]La primera merece atención: for name in marks te da las claves, no los valores. Sorprende a todo el mundo la primera vez.
Cuando quieres las dos cosas, .items() devuelve una tupla en cada vuelta — y name, mark la desempaqueta. Aquí es donde el desempaquetado del capítulo pasado demuestra su valor.
Para calcular sobre los valores, .values():
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}
print(sum(marks.values()))
print(max(marks.values()))
print(round(sum(marks.values()) / len(marks), 2))207
90
69.0Qué puede ser una clave
Para ser una clave, una cosa tiene que ser inmutable. Texto, números y tuplas funcionan todos. Una lista no:
d = {}
d[["a", "b"]] = 1TypeError: unhashable type: 'list'Esa palabra "unhashable" merece la pena desempaquetarla. Un diccionario encuentra las cosas tan rápido porque calcula un número a partir de cada clave y usa ese número para ir directo al sitio correcto. Si la clave pudiera cambiar después, ese número sería erróneo y la entrada se perdería. Así que Python se niega a dejar que cualquier cosa modificable sea una clave.
Una tupla no se puede cambiar, lo que la convierte en una clave excelente:
grid = {}
grid[(0, 0)] = "start"
grid[(1, 2)] = "tree"
print(grid)
print(grid[(1, 2)]){(0, 0): 'start', (1, 2): 'tree'}
treeEl capítulo pasado llamó a la inmutabilidad de una tupla una ventaja. Esta es la primera prueba práctica de ello.
El patrón más útil que existe — contar
words = ["pen", "bag", "pen", "ink", "pen"]
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts){'pen': 3, 'bag': 1, 'ink': 1}Lee la línea desde dentro: counts.get(word, 0) dice "dame el recuento de esta palabra hasta ahora, y cero si todavía no tiene ninguno", luego se suma uno y el resultado se vuelve a guardar.
El valor por defecto de .get() hace aquí todo el trabajo — sin él, cada palabra nueva lanzaría un KeyError y necesitarías if word in counts: y dos ramas. Escribirás este patrón de una línea muchas veces.
Un ejemplo completo
marks.py:
# A dictionary keeps a name attached to its value
marks = {"rafi": 72, "ahmed": 45, "bilal": 90, "dia": 88}
print("Everyone:")
for name, mark in marks.items():
status = "pass" if mark >= 40 else "fail"
print(f" {name:<8} {mark:>3} {status}")
print()
print("Count :", len(marks))
print("Total :", sum(marks.values()))
print("Average :", round(sum(marks.values()) / len(marks), 2))
print("Highest :", max(marks.values()))
best = max(marks, key=marks.get)
print("Best :", best, marks[best])
print("Has dia :", "dia" in marks)
print("Missing :", marks.get("nadia", "not recorded"))
marks["dia"] = 91
marks["nadia"] = 60
del marks["ahmed"]
print()
print("After edits:", marks)Everyone:
rafi 72 pass
ahmed 45 pass
bilal 90 pass
dia 88 pass
Count : 4
Total : 295
Average : 73.75
Highest : 90
Best : bilal 90
Has dia : True
Missing : not recorded
After edits: {'rafi': 72, 'bilal': 90, 'dia': 91, 'nadia': 60}Tres cosas que merece la pena notar.
status = "pass" if mark >= 40 else "fail" es una forma de escribir una condición en una línea, leyéndose desde el medio hacia afuera: ""pass" si mark >= 40, si no "fail"". El if/else de cuatro líneas del capítulo nueve aquí elige un solo valor, así que una línea basta. Escribe un if completo para una decisión de verdad — esta forma sirve solo para escoger uno de dos valores.
max(marks, key=marks.get) es el key= del capítulo trece haciendo un trabajo nuevo. (Aquí key= es el nombre de un argumento de max, no una clave de diccionario.) max(marks) daría la clave más grande, es decir, el último nombre por orden alfabético. key=marks.get dice comparar cada clave por su valor en su lugar, lo que da el nombre con la nota más alta. Merece la pena notar que sacar la nota más alta es fácil (max(marks.values())); averiguar de quién es necesita este truco.
El orden de la última línea. dia ya existía, así que su valor cambió pero su posición no. nadia es nueva, así que se añadió al final. Y ahmed ya no está. Un diccionario recuerda el orden de inserción, y reemplazar un valor no lo altera.
Cuando falla
KeyError: 'dia' No hay ninguna clave con ese nombre. Revisa la ortografía y las mayúsculas — "Rafi" y "rafi" son dos claves distintas. Las claves leídas de un archivo a menudo traen un espacio al final; print(list(d.keys())) pone comillas alrededor de cada una para que se vea. Si que falte la clave es normal, usa .get().
TypeError: unhashable type: 'list' Un intento de usar una lista como clave. Usa una tupla — tuple(my_list) crea una.
for x in my_dict no me da los valores Ni debe — eso da las claves. Para los valores usa .values(), y para las dos cosas .items().
Dos claves eran iguales y un registro desapareció Una clave existe una sola vez, y la posterior reemplazó en silencio a la anterior. Para guardar varios registros bajo un solo nombre, haz que el valor sea una lista — el tema del capítulo diecisiete.
KeyError: 0 El código escribió my_dict[0], pidiendo el primer elemento como si fuera una lista. Un diccionario no tiene posiciones; 0 se busca como clave, y cuando no hay tal clave recibes KeyError: 0. Si de verdad necesitas el primer par, escribe list(my_dict.items())[0].
.sort() no funciona en un diccionario Correcto — los diccionarios no tienen .sort(). sorted(d.items()) da una lista ordenada, que suele ser lo que querías.
Paso 4 de 6 — Predice
Comprueba lo que has entendido
Un bucle recorre directamente un diccionario. ¿Qué se imprime?
marks = {"rafi": 72, "ahmed": 45}
for name in marks:
print(name)- Arafi 72 ahmed 45
- Brafi ahmed
- C72 45
- D('rafi', 72) ('ahmed', 45)
Se pide una clave que no está. ¿Qué pasa, y cuál es la alternativa segura?
marks = {"rafi": 72}
print(marks["dia"])- AImprime `None`
- BImprime una línea en blanco
- CUn `KeyError` — la alternativa segura es `marks.get("dia")`, que da `None`
- DUn `IndexError`
El patrón de conteo. ¿Qué se imprime?
words = ["pen", "bag", "pen"]
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts)- AUn `KeyError`
- B{'pen': 2, 'bag': 1}
- C{'pen': 1, 'bag': 1}
- D{'pen': 3, 'bag': 1}
Para responder necesitas una cuenta
Inicia sesión para comprobar tus respuestas
Las preguntas están arriba, y resolverlas mentalmente es lo que de verdad importa. Inicia sesión para ver las respuestas, las explicaciones y las pistas de tres niveles.
Tu turno
Escribe un archivo llamado stock.py con un diccionario de al menos seis productos — el nombre del producto como clave, el precio como valor.
Luego:
- Imprime cada producto y su precio en columnas alineadas
- Imprime el total, la media, el precio más alto y el más bajo
- Encuentra el nombre del producto más caro, no solo el precio
- Añade un producto, cambia un precio, borra un producto, e imprime todo de nuevo
- Usa
.get()para pedir un producto que no existe, con un mensaje como valor por defecto
Luego escribe un programa de conteo: pon varias palabras en una lista, algunas repetidas, y cuenta cuántas veces aparece cada una usando el patrón counts.get(word, 0) + 1.
Por último, rompélo a propósito: pide una clave que falta con corchetes cuadrados, y prueba a usar una lista como clave. Volverán dos errores distintos — explícate cuál es cuál, y por qué.
Paso 6 de 6
Reto — el quiz del capítulo
Diez preguntas de fácil a difícil. Las últimas son difíciles a propósito.
Inicia sesión para hacer el quiz