Python & AI Tutorials Logo
Programación Python

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:

python
# 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():

python
# 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:

python
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: 87

Esta 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:

python
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:

python
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 book

El 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:

python
# 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 Math

16.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
python
# 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:

python
# 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: east

Los valores, por otro lado, pueden ser de cualquier tipo: mutables o inmutables:

python
# 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: 1

Exploraremos 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:

python
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:

python
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:

python
# 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:

python
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():

python
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:

python
# 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:

python
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:

python
# 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: en

16.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:

python
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:

python
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:

python
grades = {"Alice": 95, "Bob": 87}
 
# Eliminación segura
if "Charlie" in grades:
    del grades["Charlie"]
else:
    print("Charlie not found")  # Output: Charlie not found

16.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:

python
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:

python
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:

python
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: 0

Esto es más explícito que reasignar a un diccionario vacío (grades = {}), especialmente cuando otras variables referencian el mismo diccionario:

python
# 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 claves
  • values(): devuelve una vista de todos los valores
  • items(): devuelve una vista de todos los pares clave-valor (como tuplas)
python
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:

python
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:

python
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:

python
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)

Actualización dinámica

Sin efect

Diccionario

Objeto vista

Instantánea de lista

Modificar diccionario

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:

python
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: 3

También puedes comprobar pertenencia directamente sobre el diccionario sin llamar a keys():

python
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:

python
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: 87

16.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:

python
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: 95

La vista items() es especialmente útil para iterar, lo cual cubriremos en detalle en la siguiente sección. Aquí tienes un adelanto:

python
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: 92

16.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:

python
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
 
# Iterar sobre claves (implícito)
for name in grades:
    print(name)
 
# Output:
# Alice
# Bob
# Charlie

Esto equivale a iterar sobre grades.keys():

python
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
 
# Iterar sobre claves (explícito)
for name in grades.keys():
    print(name)
 
# Output:
# Alice
# Bob
# Charlie

Ambos 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:

python
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():

python
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
 
# Iterar sobre valores
for grade in grades.values():
    print(grade)
 
# Output:
# 95
# 87
# 92
# 88

Esto es útil cuando necesitas procesar valores pero no te importa con qué clave están asociados:

python
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:

python
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 92

Observa 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:

python
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 92

16.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:

python
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 iteration

En 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:

python
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:

python
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:

python
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 book

Esta 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:

python
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: 95

16.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:

python
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: 3

Esto es útil para comprobar si un diccionario está vacío o para informar estadísticas:

python
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.5

16.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:

python
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:

python
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.

python
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:

python
# 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_b

El operador |= fusiona in place (modifica el diccionario de la izquierda):

python
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:

python
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:

python
# 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.

Diccionario students

Alice

Bob

Charlie

age: 20

major: CS

gpa: 3.8

age: 19

major: Math

gpa: 3.6

age: 21

major: Physics

gpa: 3.9

16.7.2) Acceder a valores anidados

Para acceder a valores anidados, usa múltiples corchetes:

python
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.6

Cada 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:

python
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: Undeclared

La 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:

python
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:

python
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.9

Aquí tienes un ejemplo práctico para encontrar estudiantes que cumplen ciertos criterios:

python
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.


© 2025. Primesoft Co., Ltd.
support@primesoft.ai