16. Diccionarios: mapear claves a valores
En los capítulos anteriores, aprendimos sobre listas y tuplas: colecciones que almacenan elementos en un orden específico y nos permiten acceder a ellos por posición. Pero ¿qué pasa si queremos buscar información usando algo más significativo que un número? ¿Qué pasa si queremos encontrar la nota de un estudiante por su nombre, o el precio de un producto por su ID, o la definición de una palabra por la propia palabra?
Aquí es donde entran los diccionarios(dictionaries). Un diccionario es la estructura de datos integrada de Python para almacenar pares clave-valor(key-value pairs). En lugar de acceder a los elementos por su posición (como grades[0]), accedemos a ellos por su clave(key) (como grades["Alice"]). Esto hace que los diccionarios sean increíblemente potentes para organizar y recuperar datos en programas del mundo real.
Piensa en un diccionario como un diccionario del mundo real o una guía telefónica: buscas una palabra (la clave) para encontrar su definición (el valor), o buscas un nombre para encontrar un número de teléfono. Los diccionarios de Python funcionan de la misma manera: mapean claves a valores, lo que permite búsquedas rápidas y una organización flexible de los datos.
16.1) Crear diccionarios y acceder a valores
16.1.1) ¿Qué es un diccionario?
Un diccionario(dictionary) es una colección de pares clave-valor(key-value pairs). Cada clave está asociada con un valor, y usas la clave para recuperar el valor. Las claves deben ser únicas dentro de un diccionario: no puedes tener dos entradas con la misma clave. Sin embargo, los valores pueden duplicarse.
Aquí está la estructura básica:
- Claves(keys): identificadores únicos usados para buscar valores (como nombres, IDs o etiquetas)
- Valores(values): los datos asociados a cada clave (como notas, precios o descripciones)
Los diccionarios son:
- Mutables: puedes añadir, modificar o eliminar pares clave-valor después de crearlos
- No ordenados (en Python 3.6 y anteriores) o ordenados por inserción (en Python 3.7+): aunque Python moderno conserva el orden en el que añades los elementos, deberías pensar en los diccionarios como colecciones donde accedes a los elementos por clave, no por posición
- Dinámicos: pueden crecer y reducirse según sea necesario
16.1.2) Crear diccionarios vacíos y simples
La forma más simple de crear un diccionario es usando llaves {} con pares clave-valor separados por dos puntos:
# Diccionario vacío
empty_dict = {}
print(empty_dict) # Output: {}
print(type(empty_dict)) # Output: <class 'dict'>
# Diccionario con notas de estudiantes
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Diccionario con precios de productos
prices = {"apple": 0.50, "banana": 0.30, "orange": 0.75}
print(prices) # Output: {'apple': 0.5, 'banana': 0.3, 'orange': 0.75}Fíjate en la sintaxis: cada par clave-valor se escribe como key: value, y los pares se separan con comas. Las claves aquí son cadenas ("Alice", "apple"), y los valores son números, pero tanto las claves como los valores pueden ser de muchos tipos diferentes.
También puedes crear un diccionario usando el constructor dict():
# Usar dict() con argumentos de palabra clave
student = dict(name="Alice", age=20, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 20, 'major': 'Computer Science'}
# Usar dict() con una lista de tuplas
colors = dict([("red", "#FF0000"), ("green", "#00FF00"), ("blue", "#0000FF")])
print(colors) # Output: {'red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF'}El constructor dict() es útil cuando estás construyendo diccionarios a partir de otras estructuras de datos o cuando quieres usar identificadores de Python como claves (sin comillas).
16.1.3) Acceder a valores por clave
Para recuperar un valor de un diccionario, usa corchetes con la clave:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Acceder a valores individuales
alice_grade = grades["Alice"]
print(alice_grade) # Output: 95
bob_grade = grades["Bob"]
print(bob_grade) # Output: 87Esta es la forma más directa de acceder a valores de un diccionario. Sin embargo, si intentas acceder a una clave que no existe, Python lanza un KeyError:
grades = {"Alice": 95, "Bob": 87}
# ADVERTENCIA: KeyError - solo para demostración
# print(grades["David"]) # PROBLEM: KeyError: 'David'Este error ocurre porque "David" no es una clave en el diccionario. Aprenderemos cómo gestionar esto de forma segura en la siguiente subsección.
16.1.4) Acceso seguro con get()
Para evitar KeyError cuando una clave podría no existir, usa el método get(). Devuelve None (o un valor por defecto que especifiques) si la clave no se encuentra:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Acceso seguro con get()
alice_grade = grades.get("Alice")
print(alice_grade) # Output: 95
# La clave no existe: devuelve None
david_grade = grades.get("David")
print(david_grade) # Output: None
# Proporcionar un valor por defecto
david_grade = grades.get("David", 0)
print(david_grade) # Output: 0
# Usar get() en lógica condicional
if grades.get("Eve") is None:
print("Eve is not in the grade book") # Output: Eve is not in the grade bookEl método get() es más seguro que el acceso directo con corchetes cuando no estás seguro de que exista una clave. El segundo argumento de get() es el valor por defecto que se devolverá si falta la clave; si no proporcionas uno, el valor por defecto es None.
Aquí tienes un ejemplo práctico que muestra cuándo get() es útil:
# Base de datos de estudiantes con información opcional
students = {
"Alice": {"age": 20, "major": "CS"},
"Bob": {"age": 19}, # Bob aún no ha declarado una especialidad
"Charlie": {"major": "Math"} # La edad de Charlie no está registrada
}
# Acceder de forma segura a información potencialmente ausente
for name in ["Alice", "Bob", "Charlie"]:
student = students[name]
age = student.get("age", "Unknown")
major = student.get("major", "Undeclared")
print(f"{name}: Age {age}, Major {major}")
# Output:
# Alice: Age 20, Major CS
# Bob: Age 19, Major Undeclared
# Charlie: Age Unknown, Major Math16.1.5) Tipos de claves válidos
Las claves de un diccionario deben ser hasheables(hashable), un término técnico que significa que el valor de la clave no puede cambiar. En la práctica, esto significa:
Tipos de clave válidos (inmutables):
- Cadenas:
"name","id_123" - Números:
42,3.14 - Tuplas (si contienen solo elementos inmutables):
(1, 2),("x", "y") - Booleanos:
True,False None
Tipos de clave inválidos (mutables):
- Listas:
[1, 2, 3]no puede ser una clave - Diccionarios:
{"a": 1}no puede ser una clave - Conjuntos:
{1, 2, 3}no puede ser una clave
# Claves válidas
valid_dict = {
"name": "Alice", # Clave de cadena
42: "answer", # Clave de entero
3.14: "pi", # Clave de float
(1, 2): "coordinates", # Clave de tupla
True: "yes", # Clave booleana
None: "nothing" # Clave None
}
print(valid_dict["name"]) # Output: Alice
print(valid_dict[42]) # Output: answer
print(valid_dict[(1, 2)]) # Output: coordinates
# ADVERTENCIA: Claves inválidas - solo para demostración
# invalid_dict = {[1, 2]: "list key"} # PROBLEM: TypeError: unhashable type: 'list'
# invalid_dict = {{1, 2}: "set key"} # PROBLEM: TypeError: unhashable type: 'set'Error común de principiantes: un error frecuente es intentar usar una lista como clave de un diccionario porque parece lógico. Por ejemplo, podrías querer usar coordenadas [x, y] como clave para almacenar datos de ubicación. Cuando Python lanza TypeError: unhashable type: 'list', los principiantes a menudo no entienden por qué: al fin y al cabo, la lista contiene exactamente los datos que quieren usar como clave.
La razón es que las listas son mutables (pueden cambiar), y Python necesita que las claves de diccionario sean estables e inmutables. Si necesitas usar algo similar a una lista como clave, conviértelo primero en una tupla: tuple([1, 2]) se convierte en (1, 2), que puede usarse como clave. Las tuplas son inmutables, por lo que funcionan perfectamente:
# Mal: intentar usar una lista como clave
# locations = {[0, 0]: "origin", [1, 0]: "east"} # PROBLEM: TypeError
# Bien: convertir a tupla
locations = {(0, 0): "origin", (1, 0): "east", (0, 1): "north"}
print(locations[(0, 0)]) # Output: origin
print(locations[(1, 0)]) # Output: eastLos valores, por otro lado, pueden ser de cualquier tipo: mutables o inmutables:
# Los valores pueden ser de cualquier tipo
flexible_dict = {
"numbers": [1, 2, 3], # Valor de lista
"nested": {"a": 1, "b": 2}, # Valor de diccionario
"function": len, # Valor de función
"mixed": (1, [2, 3], {"x": 4}) # Tupla que contiene elementos mutables
}
print(flexible_dict["numbers"]) # Output: [1, 2, 3]
print(flexible_dict["nested"]["a"]) # Output: 1Exploraremos la hasheabilidad con más profundidad en el Capítulo 17, pero por ahora, recuerda: usa tipos inmutables (cadenas, números, tuplas) como claves, y estarás bien.
16.2) Añadir y actualizar entradas de diccionario
16.2.1) Añadir nuevos pares clave-valor
Añadir una nueva entrada a un diccionario es sencillo: solo asigna un valor a una nueva clave:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Añadir un nuevo estudiante
grades["Charlie"] = 92
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Añadir otro estudiante
grades["Diana"] = 88
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}Si la clave no existe, Python la crea. Si sí existe, Python actualiza el valor (lo veremos a continuación).
16.2.2) Actualizar valores existentes
Para actualizar un valor, simplemente asigna un nuevo valor a una clave existente:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Actualizar la nota de Bob
grades["Bob"] = 90
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92}
# Actualizar múltiples notas
grades["Alice"] = 98
grades["Charlie"] = 94
print(grades) # Output: {'Alice': 98, 'Bob': 90, 'Charlie': 94}Python no distingue entre añadir y actualizar: la sintaxis es idéntica. Si la clave existe, el valor se actualiza; si no, se crea una nueva entrada.
Aquí tienes un ejemplo práctico que muestra tanto añadir como actualizar:
# Llevar el inventario
inventory = {"apple": 50, "banana": 30}
print("Initial inventory:", inventory) # Output: Initial inventory: {'apple': 50, 'banana': 30}
# Reponer manzanas (actualizar existente)
inventory["apple"] = inventory["apple"] + 20
print("After restocking apples:", inventory) # Output: After restocking apples: {'apple': 70, 'banana': 30}
# Añadir un nuevo producto (añadir clave nueva)
inventory["orange"] = 40
print("After adding oranges:", inventory) # Output: After adding oranges: {'apple': 70, 'banana': 30, 'orange': 40}
# Vender algunas bananas (actualizar existente)
inventory["banana"] = inventory["banana"] - 10
print("After selling bananas:", inventory) # Output: After selling bananas: {'apple': 70, 'banana': 20, 'orange': 40}16.2.3) Usar update() para fusionar diccionarios
El método update() añade varios pares clave-valor de una vez o fusiona otro diccionario en el actual:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Añadir varios estudiantes a la vez
new_students = {"Charlie": 92, "Diana": 88}
grades.update(new_students)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Actualizar existentes y añadir nuevos
more_updates = {"Bob": 90, "Eve": 85} # Cambia la nota de Bob, Eve es nueva
grades.update(more_updates)
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92, 'Diana': 88, 'Eve': 85}El método update() modifica el diccionario in place. Si una clave ya existe, su valor se actualiza; si no, se añade el par clave-valor.
También puedes pasar argumentos de palabra clave a update():
student = {"name": "Alice", "age": 20}
print(student) # Output: {'name': 'Alice', 'age': 20}
# Actualizar usando argumentos de palabra clave
student.update(age=21, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 21, 'major': 'Computer Science'}Aquí tienes un ejemplo práctico fusionando ajustes de configuración:
# Configuración predeterminada
config = {
"theme": "light",
"font_size": 12,
"auto_save": True
}
# Preferencias del usuario (sobrescriben algunos valores por defecto)
user_prefs = {
"theme": "dark",
"font_size": 14
}
# Fusionar las preferencias del usuario en config
config.update(user_prefs)
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}16.2.4) Usar setdefault() para añadir solo si la clave no existe
El método setdefault() es útil cuando quieres añadir un par clave-valor solo si la clave no existe ya. Si la clave existe, devuelve el valor actual sin cambiarlo:
grades = {"Alice": 95, "Bob": 87}
# Añadir Charlie (la clave no existe)
result = grades.setdefault("Charlie", 90)
print(result) # Output: 90
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90}
# Intentar añadir Alice (la clave existe: sin cambio)
result = grades.setdefault("Alice", 80)
print(result) # Output: 95 (existing value returned)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90} (unchanged)Esto es especialmente útil cuando quieres asegurarte de que existan todas las claves de configuración requeridas con valores por defecto, preservando a la vez cualquier valor que el usuario ya haya personalizado:
# Configuración de la aplicación con valores por defecto
config = {"theme": "light", "font_size": 12}
# Asegurar que existen todos los ajustes requeridos con valores por defecto
config.setdefault("auto_save", True)
config.setdefault("language", "en")
config.setdefault("theme", "dark") # No cambiará: ya existe
print(config)
# Output: {'theme': 'light', 'font_size': 12, 'auto_save': True, 'language': 'en'}
# Ahora accede de forma segura a todos los ajustes
print(f"Theme: {config['theme']}") # Output: Theme: light
print(f"Auto-save: {config['auto_save']}") # Output: Auto-save: True
print(f"Language: {config['language']}") # Output: Language: en16.3) Eliminar entradas de diccionario con del y pop()
16.3.1) Eliminar entradas con del
La sentencia del elimina un par clave-valor de un diccionario:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Eliminar a Charlie
del grades["Charlie"]
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Diana': 88}
# Eliminar a otro estudiante
del grades["Bob"]
print(grades) # Output: {'Alice': 95, 'Diana': 88}Si intentas eliminar una clave que no existe, Python lanza un KeyError:
grades = {"Alice": 95, "Bob": 87}
# ADVERTENCIA: KeyError - solo para demostración
# del grades["Charlie"] # PROBLEM: KeyError: 'Charlie'Para eliminar de forma segura una clave que podría no existir, comprueba primero:
grades = {"Alice": 95, "Bob": 87}
# Eliminación segura
if "Charlie" in grades:
del grades["Charlie"]
else:
print("Charlie not found") # Output: Charlie not found16.3.2) Eliminar y recuperar con pop()
El método pop() elimina una clave y devuelve su valor. Esto es útil cuando necesitas tanto eliminar una entrada como usar su valor:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Eliminar y obtener la nota de Bob
bob_grade = grades.pop("Bob")
print(bob_grade) # Output: 87
print(grades) # Output: {'Alice': 95, 'Charlie': 92}
# Eliminar y usar el valor
charlie_grade = grades.pop("Charlie")
print(f"Charlie's final grade was {charlie_grade}") # Output: Charlie's final grade was 92
print(grades) # Output: {'Alice': 95}Al igual que del, pop() lanza un KeyError si la clave no existe. Sin embargo, puedes proporcionar un valor por defecto para que se devuelva en su lugar:
grades = {"Alice": 95, "Bob": 87}
# Pop con valor por defecto (la clave no existe)
diana_grade = grades.pop("Diana", 0)
print(diana_grade) # Output: 0
print(grades) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
# Pop con valor por defecto (la clave existe)
alice_grade = grades.pop("Alice", 0)
print(alice_grade) # Output: 95
print(grades) # Output: {'Bob': 87}16.3.3) Eliminar todas las entradas con clear()
El método clear() elimina todos los pares clave-valor de un diccionario, dejándolo vacío:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Vaciar todas las entradas
grades.clear()
print(grades) # Output: {}
print(len(grades)) # Output: 0Esto es más explícito que reasignar a un diccionario vacío (grades = {}), especialmente cuando otras variables referencian el mismo diccionario:
# Demostrar la diferencia
grades = {"Alice": 95, "Bob": 87}
backup = grades # backup referencia el mismo diccionario
# Usar clear() - afecta a ambas variables
grades.clear()
print(grades) # Output: {}
print(backup) # Output: {} (same dictionary was cleared)
# Restablecer para el siguiente ejemplo
grades = {"Alice": 95, "Bob": 87}
backup = grades
# Reasignar - solo afecta a grades
grades = {}
print(grades) # Output: {}
print(backup) # Output: {'Alice': 95, 'Bob': 87} (different dictionary now)Exploraremos este comportamiento con más detalle en el Capítulo 18 cuando hablemos de la semántica de referencias, pero por ahora, recuerda: clear() vacía el diccionario existente, mientras que la reasignación crea un nuevo diccionario vacío.
16.4) Objetos vista de diccionario: keys(), values() e items()
16.4.1) Comprender las vistas de diccionario
Los diccionarios proporcionan tres métodos que devuelven objetos vista(view objects): objetos especiales que ofrecen una vista dinámica de las claves, valores o pares clave-valor del diccionario. Estas vistas reflejan los cambios en el diccionario automáticamente:
keys(): devuelve una vista de todas las clavesvalues(): devuelve una vista de todos los valoresitems(): devuelve una vista de todos los pares clave-valor (como tuplas)
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Obtener vistas
keys_view = grades.keys()
values_view = grades.values()
items_view = grades.items()
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
print(values_view) # Output: dict_values([95, 87, 92])
print(items_view) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])Estas no son listas: son objetos vista. Pero puedes convertirlas a listas si lo necesitas:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Convertir vistas a listas
keys_list = list(grades.keys())
values_list = list(grades.values())
items_list = list(grades.items())
print(keys_list) # Output: ['Alice', 'Bob', 'Charlie']
print(values_list) # Output: [95, 87, 92]
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]16.4.2) Las vistas son dinámicas
Los objetos vista reflejan los cambios en el diccionario automáticamente:
grades = {"Alice": 95, "Bob": 87}
# Crear una vista
keys_view = grades.keys()
print(keys_view) # Output: dict_keys(['Alice', 'Bob'])
# Modificar el diccionario
grades["Charlie"] = 92
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Eliminar una entrada
del grades["Bob"]
print(keys_view) # Output: dict_keys(['Alice', 'Charlie'])Este comportamiento dinámico es útil cuando necesitas trabajar con el contenido de un diccionario que podría cambiar. Sin embargo, si necesitas una instantánea que no cambie, convierte la vista a una lista:
grades = {"Alice": 95, "Bob": 87}
# Crear una instantánea
keys_snapshot = list(grades.keys())
print(keys_snapshot) # Output: ['Alice', 'Bob']
# Modificar el diccionario
grades["Charlie"] = 92
print(keys_snapshot) # Output: ['Alice', 'Bob'] (unchanged)16.4.3) Trabajar con keys()
El método keys() devuelve una vista de todas las claves del diccionario. Esto es útil para comprobar qué claves existen o para iterar sobre ellas:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Obtener todas las claves
keys = grades.keys()
print(keys) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Comprobar si existe una clave
if "Alice" in keys:
print("Alice is in the grade book") # Output: Alice is in the grade book
# Contar claves
print(f"Number of students: {len(keys)}") # Output: Number of students: 3También puedes comprobar pertenencia directamente sobre el diccionario sin llamar a keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Estas son equivalentes
if "Alice" in grades.keys():
print("Found (using keys())")
if "Alice" in grades:
print("Found (direct check)") # This is more common and concise
# Output:
# Found (using keys())
# Found (direct check)16.4.4) Trabajar con values()
El método values() devuelve una vista de todos los valores del diccionario. Esto es útil cuando necesitas procesar valores sin preocuparte por sus claves:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Obtener todos los valores
values = grades.values()
print(values) # Output: dict_values([95, 87, 92, 88])
# Calcular estadísticas
total = sum(values)
count = len(values)
average = total / count
print(f"Total points: {total}") # Output: Total points: 362
print(f"Number of students: {count}") # Output: Number of students: 4
print(f"Average grade: {average}") # Output: Average grade: 90.5
# Encontrar las notas más altas y más bajas
print(f"Highest grade: {max(values)}") # Output: Highest grade: 95
print(f"Lowest grade: {min(values)}") # Output: Lowest grade: 8716.4.5) Trabajar con items()
El método items() devuelve una vista de pares clave-valor como tuplas. Esta es la vista más utilizada porque te da tanto claves como valores:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Obtener todos los pares clave-valor
items = grades.items()
print(items) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])
# Convertir a lista para ver las tuplas claramente
items_list = list(items)
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]
# Acceder a tuplas individuales
first_item = items_list[0]
print(first_item) # Output: ('Alice', 95)
print(first_item[0]) # Output: Alice
print(first_item[1]) # Output: 95La vista items() es especialmente útil para iterar, lo cual cubriremos en detalle en la siguiente sección. Aquí tienes un adelanto:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Procesar cada par clave-valor
for name, grade in grades.items():
print(f"{name}: {grade}")
# Output:
# Alice: 95
# Bob: 87
# Charlie: 9216.5) Iterar sobre claves, valores e items
16.5.1) Iterar sobre claves (comportamiento por defecto)
Cuando iteras sobre un diccionario directamente con un bucle(loop) for, iteras sobre sus claves:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterar sobre claves (implícito)
for name in grades:
print(name)
# Output:
# Alice
# Bob
# CharlieEsto equivale a iterar sobre grades.keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterar sobre claves (explícito)
for name in grades.keys():
print(name)
# Output:
# Alice
# Bob
# CharlieAmbos enfoques funcionan de forma idéntica. La versión implícita (sin .keys()) es más común y concisa.
Aquí tienes un ejemplo práctico usando claves para acceder a valores:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Encontrar estudiantes que aprobaron (grade >= 90)
passing_students = []
for name in grades:
if grades[name] >= 90:
passing_students.append(name)
print("Students who passed:", passing_students) # Output: Students who passed: ['Alice', 'Charlie']16.5.2) Iterar sobre valores
Para iterar solo sobre los valores, usa values():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Iterar sobre valores
for grade in grades.values():
print(grade)
# Output:
# 95
# 87
# 92
# 88Esto es útil cuando necesitas procesar valores pero no te importa con qué clave están asociados:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Calcular total y promedio
total = 0
count = 0
for grade in grades.values():
total = total + grade
count = count + 1
average = total / count
print(f"Class average: {average}") # Output: Class average: 90.5
# Comprobar si todos los estudiantes aprobaron
all_passed = True
for grade in grades.values():
if grade < 60:
all_passed = False
break
if all_passed:
print("All students passed!") # Output: All students passed!16.5.3) Iterar sobre pares clave-valor con items()
El patrón de iteración más común y útil es usar items() para obtener tanto claves como valores:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterar sobre pares clave-valor
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Output:
# Alice scored 95
# Bob scored 87
# Charlie scored 92Observa el desempaquetado de tuplas: for name, grade in grades.items(). Cada elemento es una tupla como ("Alice", 95), y la desempaquetamos en dos variables. Esto es mucho más legible que acceder a índices de tupla:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Sin desempaquetar (menos legible)
for item in grades.items():
print(f"{item[0]} scored {item[1]}")
# Con desempaquetado (más legible)
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Both produce the same output:
# Alice scored 95
# Bob scored 87
# Charlie scored 9216.5.4) Modificar diccionarios durante la iteración
Advertencia: Modificar el tamaño de un diccionario (añadir o eliminar claves) mientras iteras sobre él puede causar errores o un comportamiento inesperado:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# ADVERTENCIA: RuntimeError - solo para demostración
# for name in grades:
# if grades[name] < 90:
# del grades[name] # PROBLEM: RuntimeError: dictionary changed size during iterationEn Python moderno (3.7+), esto lanza inmediatamente un RuntimeError en cuanto intentas cambiar el tamaño del diccionario. Python detecta la modificación y detiene la ejecución para evitar un comportamiento impredecible.
En versiones antiguas de Python, esto podría hacer que el iterador:
- Se saltara elementos que debería procesar
- Procesara el mismo elemento dos veces
- Produjera resultados inconsistentes
Por eso Python ahora falla rápido con un mensaje de error claro.
Si necesitas modificar un diccionario durante la iteración, itera sobre una copia de las claves:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Seguro: iterar sobre una copia de las claves
for name in list(grades.keys()):
if grades[name] < 90:
del grades[name]
print(grades) # Output: {'Alice': 95, 'Charlie': 92}O construye un nuevo diccionario con solo las entradas que quieras:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Construir un nuevo diccionario con entradas filtradas
high_grades = {}
for name, grade in grades.items():
if grade >= 90:
high_grades[name] = grade
print(high_grades) # Output: {'Alice': 95, 'Charlie': 92}El segundo enfoque suele ser más claro y seguro. Aprenderemos formas aún más elegantes de hacer esto usando comprensiones de diccionario en el Capítulo 35.
16.6) Métodos comunes de diccionarios
16.6.1) Comprobar claves con in y not in
Los operadores in y not in comprueban si existe una clave en un diccionario:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Comprobar si existen claves
if "Alice" in grades:
print("Alice is in the grade book") # Output: Alice is in the grade book
if "David" not in grades:
print("David is not in the grade book") # Output: David is not in the grade bookEsta es la forma preferida de comprobar la existencia de una clave antes de acceder a valores. Es más legible y más Pythonic que usar get() y comprobar None:
grades = {"Alice": 95, "Bob": 87}
# Preferido: usar in
if "Alice" in grades:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 95
# Alternativa: usar get() y comprobar None
if grades.get("Alice") is not None:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 9516.6.2) Obtener el número de entradas con len()
La función len() devuelve el número de pares clave-valor en un diccionario:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(len(grades)) # Output: 3
# Diccionario vacío
empty = {}
print(len(empty)) # Output: 0
# Después de modificaciones
grades["Diana"] = 88
print(len(grades)) # Output: 4
del grades["Bob"]
print(len(grades)) # Output: 3Esto es útil para comprobar si un diccionario está vacío o para informar estadísticas:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
if len(grades) == 0:
print("No students in grade book")
else:
total = sum(grades.values())
average = total / len(grades)
print(f"{len(grades)} students, average grade: {average}")
# Output: 4 students, average grade: 90.516.6.3) Copiar diccionarios con copy()
El método copy() crea una copia superficial(shallow copy) de un diccionario: un nuevo diccionario con los mismos pares clave-valor:
original = {"Alice": 95, "Bob": 87}
duplicate = original.copy()
print(original) # Output: {'Alice': 95, 'Bob': 87}
print(duplicate) # Output: {'Alice': 95, 'Bob': 87}
# Modificar la copia
duplicate["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
print(duplicate) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Esto es diferente de la asignación simple, que crea una referencia al mismo diccionario:
original = {"Alice": 95, "Bob": 87}
reference = original # No es una copia: es el mismo diccionario
# Modificar a través de la referencia
reference["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92} (changed!)
print(reference) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Exploraremos las copias superficiales vs profundas en detalle en el Capítulo 18. Por ahora, recuerda: usa copy() cuando quieras un duplicado independiente de un diccionario.
16.6.4) Fusionar diccionarios con el operador | (Python 3.9+)
Python 3.9 introdujo el operador | para fusionar diccionarios. El operador | crea un nuevo diccionario que combina todas las claves de ambos diccionarios. Para claves duplicadas, el valor del lado derecho sobrescribe el valor del lado izquierdo. Ambos diccionarios originales permanecen sin cambios.
defaults = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Fusionar diccionarios (user_prefs sobrescribe defaults)
config = defaults | user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}
# Diccionarios originales sin cambios
print(defaults) # Output: {'theme': 'light', 'font_size': 12, 'auto_save': True}
print(user_prefs) # Output: {'theme': 'dark', 'font_size': 14}Aquí tienes otro ejemplo que muestra cómo esto es útil para combinar datos de múltiples fuentes:
# Información de productos de dos proveedores
supplier_a = {
"laptop": {"price": 999.99, "stock": 15},
"mouse": {"price": 29.99, "stock": 50}
}
supplier_b = {
"laptop": {"price": 949.99, "stock": 20}, # Mejor precio y stock
"keyboard": {"price": 79.99, "stock": 30}
}
# Fusionar: los datos de supplier_b sobrescriben a supplier_a para productos coincidentes
combined = supplier_a | supplier_b
print(combined)
# Output: {'laptop': {'price': 949.99, 'stock': 20}, 'mouse': {'price': 29.99, 'stock': 50}, 'keyboard': {'price': 79.99, 'stock': 30}}
# Ahora tenemos laptop de supplier_b (mejor oferta), mouse de supplier_a y keyboard de supplier_bEl operador |= fusiona in place (modifica el diccionario de la izquierda):
config = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Fusionar in place
config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}Esto equivale a usar update() pero es más conciso:
config = {"theme": "light", "font_size": 12}
user_prefs = {"theme": "dark", "font_size": 14}
# Estos son equivalentes
config.update(user_prefs)
# config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14}16.7) Diccionarios anidados para datos estructurados
16.7.1) Crear diccionarios anidados
¿Por qué usar diccionarios anidados? Imagina hacer seguimiento de la información de estudiantes. Podrías crear diccionarios separados: ages = {"Alice": 20, "Bob": 19}, majors = {"Alice": "CS", "Bob": "Math"}, gpas = {"Alice": 3.8, "Bob": 3.6}. Pero esto se vuelve inmanejable: tienes que mantener sincronizados tres diccionarios, y buscar toda la información de un estudiante requiere tres búsquedas separadas. Los diccionarios anidados resuelven esto agrupando datos relacionados: un nombre de estudiante mapea a toda su información en una sola búsqueda.
Un diccionario anidado(nested dictionary) es un diccionario que contiene otros diccionarios como valores. Esto es útil para representar datos estructurados y jerárquicos:
# Registros de estudiantes con múltiples atributos
students = {
"Alice": {
"age": 20,
"major": "Computer Science",
"gpa": 3.8
},
"Bob": {
"age": 19,
"major": "Mathematics",
"gpa": 3.6
},
"Charlie": {
"age": 21,
"major": "Physics",
"gpa": 3.9
}
}
print(students)
# Output: {'Alice': {'age': 20, 'major': 'Computer Science', 'gpa': 3.8}, 'Bob': {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}, 'Charlie': {'age': 21, 'major': 'Physics', 'gpa': 3.9}}Cada nombre de estudiante mapea a otro diccionario que contiene sus atributos. Esta estructura es mucho más flexible y mantenible que usar diccionarios separados para cada atributo.
16.7.2) Acceder a valores anidados
Para acceder a valores anidados, usa múltiples corchetes:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Acceder a valores anidados
alice_age = students["Alice"]["age"]
print(alice_age) # Output: 20
bob_major = students["Bob"]["major"]
print(bob_major) # Output: Mathematics
# Usar en expresiones
print(f"Alice is {students['Alice']['age']} years old")
# Output: Alice is 20 years old
print(f"Bob's GPA: {students['Bob']['gpa']}")
# Output: Bob's GPA: 3.6Cada corchete accede a un nivel de anidamiento. Primero students["Alice"] obtiene el diccionario interno, y luego ["age"] obtiene el valor de la edad de ese diccionario.
16.7.3) Acceder de forma segura a valores anidados
Al acceder a diccionarios anidados, necesitas comprobar que existe cada nivel:
students = {
"Alice": {"age": 20, "major": "Computer Science"},
"Bob": {"age": 19} # Bob aún no ha declarado una especialidad
}
# Acceso inseguro: podría lanzar KeyError
# print(students["Bob"]["major"]) # PROBLEM: KeyError: 'major'
# Acceso seguro con múltiples comprobaciones
if "Bob" in students:
if "major" in students["Bob"]:
print(f"Bob's major: {students['Bob']['major']}")
else:
print("Bob hasn't declared a major") # Output: Bob hasn't declared a major
# Usar get() para acceso anidado seguro
bob_major = students.get("Bob", {}).get("major", "Undeclared")
print(f"Bob's major: {bob_major}") # Output: Bob's major: UndeclaredLa cadena de get() funciona porque si "Bob" no existe, get() devuelve un diccionario vacío {}, y luego el segundo get() devuelve de forma segura "Undeclared".
16.7.4) Modificar diccionarios anidados
Puedes añadir, actualizar o eliminar entradas en diccionarios anidados:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Actualizar un valor anidado
students["Alice"]["gpa"] = 3.9
print(f"Alice's new GPA: {students['Alice']['gpa']}") # Output: Alice's new GPA: 3.9
# Añadir un nuevo atributo a un estudiante existente
students["Bob"]["email"] = "bob@university.edu"
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6, 'email': 'bob@university.edu'}
# Añadir un nuevo estudiante con datos anidados
students["Charlie"] = {
"age": 21,
"major": "Physics",
"gpa": 3.7
}
print(f"Number of students: {len(students)}") # Output: Number of students: 3
# Eliminar un atributo
del students["Bob"]["email"]
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}16.7.5) Iterar sobre diccionarios anidados
Puedes iterar sobre diccionarios anidados usando bucles anidados:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9}
}
# Iterar sobre estudiantes y sus atributos
for name, info in students.items():
print(f"\n{name}:")
for key, value in info.items():
print(f" {key}: {value}")
# Output:
# Alice:
# age: 20
# major: Computer Science
# gpa: 3.8
#
# Bob:
# age: 19
# major: Mathematics
# gpa: 3.6
#
# Charlie:
# age: 21
# major: Physics
# gpa: 3.9Aquí tienes un ejemplo práctico para encontrar estudiantes que cumplen ciertos criterios:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9},
"Diana": {"age": 20, "major": "Computer Science", "gpa": 3.5}
}
# Encontrar estudiantes de CS con GPA por encima de 3.7
print("High-performing CS students:")
for name, info in students.items():
if info["major"] == "Computer Science" and info["gpa"] >= 3.7:
print(f" {name} (GPA: {info['gpa']})")
# Output:
# High-performing CS students:
# Alice (GPA: 3.8)Los diccionarios son una de las estructuras de datos más potentes y versátiles de Python. Proporcionan búsquedas rápidas, organización flexible y soluciones elegantes a problemas comunes de programación. A medida que sigas aprendiendo Python, verás diccionarios por todas partes: desde ajustes de configuración hasta procesamiento de datos y la construcción de aplicaciones complejas.
Los patrones que hemos cubierto en este capítulo (conteo, agrupación, tablas de búsqueda y transformación de datos) forman la base para trabajar con datos estructurados en Python. En el próximo capítulo, exploraremos los conjuntos, otro tipo de colección que complementa a los diccionarios para trabajar con datos únicos y no ordenados.