Contenido del curso
Fundamentos de Python
Los fundamentos de Python incluyen la sintaxis (sangría para bloques de código), los tipos de datos básicos (numéricos, booleanos, cadenas de texto), las variables, el control de flujo (condicionales como if/elif/else y bucles como for/while), y las funciones (bloques de código reutilizables).
0/6
Operadores y Control de Flujo
Los operadores en Python se clasifican en varios tipos (aritméticos, de comparación, lógicos, de asignación, etc.), mientras que el control de flujo se refiere al orden en que se ejecutan las instrucciones, modificándolo con estructuras como if, elif, else (condicionales), y for o while (bucles). Las instrucciones break, continue y pass también controlan el flujo dentro de los bucles.
0/3
Funciones y Manejo de Errores
Las funciones en Python son bloques de código reutilizables, mientras que el manejo de errores (excepciones) se hace con los bloques try, except, else y finally para gestionar errores de ejecución y evitar que el programa se detenga abruptamente. try ejecuta un código, except lo captura si ocurre un error específico, else se ejecuta si no hay error y finally se ejecuta siempre, haya o no error.
0/3
Estructuras de Datos
Las estructuras de datos principales en Python son las listas, tuplas, diccionarios y conjuntos. Estos tipos de datos se diferencian por su mutabilidad (si sus elementos se pueden cambiar después de su creación) y si mantienen el orden de los elementos. Las listas son ordenadas y mutables, mientras que las tuplas son ordenadas e inmutables. Los diccionarios son colecciones no ordenadas de pares clave-valor, y los conjuntos son colecciones desordenadas de elementos únicos.
0/5
Programación Orientada a Objetos (POO)
La Programación Orientada a Objetos (POO) en Python es un paradigma que organiza el código en torno a objetos, que son instancias de clases. Las clases actúan como plantillas que definen los atributos (datos) y métodos (comportamientos) de los objetos, permitiendo crear programas más modularizados, reutilizables y fáciles de mantener. Python soporta conceptos clave de la POO como la herencia, el encapsulamiento y el polimorfismo.
0/5
Ambientes virtuales
Un entorno virtual de Python es un espacio aislado que permite instalar paquetes y dependencias específicos para un proyecto concreto sin afectar a otras aplicaciones o a la instalación global de Python. Se crea una carpeta con una instalación de Python y una copia local de pip dentro de este entorno, lo que permite a cada proyecto tener sus propias bibliotecas y versiones, evitando así conflictos entre diferentes proyectos que puedan requerir versiones distintas de la misma librería.
0/1
Archivos
El manejo de archivos en Python se realiza principalmente usando la función open() para abrir un archivo y los métodos read(), write(), append() y close() para manipularlo. Es crucial gestionar los archivos adecuadamente, cerrándolos para liberar recursos, aunque es más recomendable usar la sentencia with, que cierra el archivo automáticamente. Python permite trabajar con archivos de texto y binarios, así como con distintos modos de apertura como 'r' (solo lectura), 'w' (escritura/sobreescritura), y 'a' (añadir).
0/1
Módulos y Librerías Estándar
Un "módulo" en Python se refiere a dos conceptos distintos: un archivo .py con código que se puede importar para reutilizar funciones, clases y variables, y el operador % que calcula el residuo de una división entera. Ambos son útiles para organizar el código y resolver problemas matemáticos, respectivamente.
0/2
Hilos y tareas en Python
En Python, los hilos (threads) son secuencias de ejecución dentro de un proceso que permiten la concurrencia, ejecutando tareas simultáneamente para aprovechar mejor los recursos del sistema. Las tareas son las unidades de trabajo a realizar, como descargar archivos o procesar datos. Se utilizan para manejar operaciones que implican espera (I/O-bound) de forma eficiente, permitiendo que una aplicación no se bloquee mientras espera. Para ello, se usa el módulo threading, se crean objetos Thread que representan las tareas, se inician con .start() y se pueden sincronizar con mecanismos como Lock para evitar conflictos.
0/1
Curso de Programación en Pythón 3.

    📘 Parte 1 — Pares clave-valor

    Un diccionario (dict) es una estructura de datos que almacena información en
    pares clave-valor. Cada clave identifica un valor. Es perfecto para modelar
    objetos del mundo real: usuarios, productos, configuraciones, etc.


    ✅ Sintaxis básica

    
    # Diccionario literal con llaves
    usuario = {
        "nombre": "Javier",
        "edad": 25,
        "activo": True
    }
    print(usuario["nombre"])  # >> Javier
      

    Lee la idea así: “la clave nombre apunta al valor Javier”.


    🧩 ¿Qué puede ser una clave?

    • Debe ser hashable (inmutable): str, int, float, bool, tuple (si sus elementos son hashables)…
    • No pueden ser claves tipos mutables: list, dict, set.
    • Las claves son únicas. Una clave repetida sobrescribe el valor anterior.
    
    ok = { ("ES","MAD"): "Madrid", ("ES","SEV"): "Sevilla" }  # tuplas como clave
    bad = { ["ES","MAD"]: "Madrid" }  # ❌ TypeError: unhashable type: 'list'
      
    Tip: usa tuplas para claves compuestas (p. ej., (año, mes)).

    📦 Crear diccionarios — formas comunes

    MétodoEjemploNotas
    Literal{"a":1, "b":2}La forma más clara
    Constructor dict()dict(a=1, b=2)Solo para claves válidas como identificadores
    Desde paresdict([("a",1), ("b",2)])Útil al transformar listas/tuplas
    Comprehension{x: x*x for x in range(3)}Potente y expresivo
    fromkeysdict.fromkeys(["a","b"], 0)Inicializa varias claves con un valor
    
    # Ejemplos rápidos
    d1 = {"user": "javi", "rol": "admin"}
    d2 = dict(user="javi", rol="admin")
    d3 = dict([("user","javi"), ("rol","admin")])
    d4 = {k: len(k) for k in ("manzana","pera","uva")}
    d5 = dict.fromkeys(["id","estado","total"], None)
      

    🗂️ Valores: cualquier objeto

    Un valor puede ser de cualquier tipo: números, cadenas, listas, otros diccionarios, funciones, etc.

    
    producto = {
        "sku": 101,
        "nombre": "Teclado",
        "precio": 29.9,
        "tags": ["periférico", "usb"],
        "stock": {"MAD": 12, "BCN": 8}
    }
      

    🧠 Propiedades importantes

    • 📥 Orden de inserción preservado (Python 3.7+): al iterar verás las claves en el orden en que se añadieron.
    • Acceso por clave en tiempo promedio O(1): muy rápido para búsquedas.
    • 🔐 Unicidad de clave: asignar de nuevo la misma clave sobrescribe el valor.
    
    d = {"a":1, "b":2, "a":99}
    print(d)  # {'a': 99, 'b': 2}  ← 'a' se sobrescribió
      

    🔎 Acceso seguro vs. acceso directo

    Acceso directo con [] lanza error si la clave no existe. Con get() puedes definir un valor por defecto.

    
    usuario = {"nombre": "Javi"}
    
    # Acceso directo (puede fallar)
    # print(usuario["edad"])  # ❌ KeyError
    
    # Acceso seguro con .get()
    print(usuario.get("edad"))           # None
    print(usuario.get("edad", "N/A"))    # N/A
      

    🎯 Casos de uso típicos (clave-valor)

    1) Perfil de usuario

    
    perfil = {"usuario":"jcachon", "email":"javi@example.com", "premium": True}
    print(perfil["usuario"])
      

    2) Índices y catálogos

    
    precios = {"pan": 1.1, "leche": 1.0, "queso": 4.5}
    print(precios["queso"])
      

    3) Traducciones / mapeos

    
    es_en = {"hola":"hello", "adios":"goodbye"}
    print(es_en.get("hola","(missing)"))
      

    📋 Tabla rápida — operaciones básicas (vista previa)

    TareaEjemploQué hace
    Accederd["k"] / d.get("k","def")Obtiene el valor (directo/seguro)
    Insertar/Actualizard["k"] = vCrea o sobrescribe
    Comprobar clave"k" in dDevuelve True/False
    Tamañolen(d)Cuenta claves
    Vaciard.clear()Elimina todo

    🔜 En la Parte 2 verás modificación y eliminación en detalle: update(),
    del, pop(), popitem()


    ⚠️ Errores comunes (y cómo evitarlos)

    • ❌ Usar tipos mutables como clave (list, dict): “unhashable type”.
      ✅ Solución: usa tuplas u otro tipo inmutable.
    • ❌ Dar por hecho que la clave existe y usar [].
      ✅ Solución: usa .get() con valor por defecto si no estás seguro.
    • ❌ Repetir claves en el literal y creer que se “guardan ambas”.
      ✅ Solución: recuerda que la última asignación gana.

    🧪 Mini-ejercicios

    1. Crea un dict persona con nombre, edad, ciudad. Imprime persona["nombre"].
    2. Usa .get() para obtener telefono con valor por defecto "N/D".
    3. Crea un mapa de precios (producto → precio) y consulta el de "manzana".
    4. Usa una tupla como clave compuesta para guardar la temperatura de ("Madrid","Enero").
    5. Escribe una comprensión de diccionario que mapee n a n*n para n en 1..5.

    🏁 Cierre de la Parte 1

    Has entendido el corazón de los diccionarios: los pares clave-valor.
    En la Parte 2 veremos cómo acceder, modificar y eliminar de forma segura y profesional,
    con patrones que evitarán errores en producción.

    🛠️ Parte 2 — Acceso, modificación y eliminación

    Ahora que ya dominas la idea de clave → valor, toca operar un diccionario como un pro:
    acceder de forma segura, modificar sin sorpresas y eliminar con intención clara.
    Verás atajos útiles como get(), setdefault(), update() y los operadores de fusión | y |= (Python ≥ 3.9).


    🔎 Acceso a valores

    MétodoEjemploComportamiento
    Acceso directousuario["email"]Devuelve el valor o lanza KeyError si no existe
    Acceso segurousuario.get("email")Devuelve el valor o None (o un defecto) si no existe
    Acceso con valor por defectousuario.get("email", "N/D")Evita excepciones y documenta intención
    Crear si faltausuario.setdefault("email", "N/D")Devuelve el valor; si no existe, lo crea con el defecto
    
    usuario = {"nombre": "Javi"}
    
    # Acceso directo (riesgo KeyError):
    # print(usuario["email"])
    
    print(usuario.get("email"))            # None
    print(usuario.get("email", "N/D"))     # N/D
    print(usuario.setdefault("email", "N/D"))  # crea 'email' si no existía
    print(usuario)                         # {'nombre': 'Javi', 'email': 'N/D'}
      
    Rule of thumb: si la clave podría no existir, usa .get(). Si además quieres crear la clave cuando falte, usa .setdefault().

    ✍️ Inserción y actualización

    Asignación simple

    
    perfil = {"user": "jcachon"}
    perfil["rol"] = "admin"        # inserta si no existe
    perfil["rol"] = "editor"       # sobrescribe si existe
    print(perfil)                  # {'user':'jcachon','rol':'editor'}
      

    Actualizar varios campos: update()

    
    perfil.update({"activo": True, "email": "javi@example.com"})
    # También acepta pares clave-valor o keywords:
    perfil.update([("pais","ES")], ciudad="Madrid")
    print(perfil)
      

    Fusión de diccionarios (Python ≥ 3.9): | y |=

    
    a = {"a":1, "b":2}
    b = {"b":99, "c":3}
    
    c = a | b     # nuevo dict (a no cambia)
    print(c)      # {'a':1,'b':99,'c':3}
    
    a |= b        # fusiona en 'a' (in place)
    print(a)      # {'a':1,'b':99,'c':3}
      

    Contadores y acumulación: get() vs setdefault()

    
    texto = "banana"
    freq = {}
    for ch in texto:
        freq[ch] = freq.get(ch, 0) + 1   # patrón clásico con get()
    
    # Alternativa con setdefault():
    freq2 = {}
    for ch in texto:
        freq2.setdefault(ch, 0)
        freq2[ch] += 1
    
    print(freq, freq2)
      

    🧹 Eliminación de elementos

    MétodoEjemploQué devuelveNotas
    deldel d["k"]Lanza KeyError si no existe
    pop()d.pop("k")Valor eliminadoCon segundo arg, devuelve ese defecto si no existe
    popitem()d.popitem()Par (clave, valor)Elimina el último par (LIFO en 3.7+)
    clear()d.clear()Vacía el diccionario
    
    d = {"a":1, "b":2, "c":3}
    
    # del
    del d["b"]
    # pop con valor por defecto (evita KeyError)
    valor = d.pop("x", None)   # None si no existe 'x'
    
    # popitem: quita el último par
    clave, val = d.popitem()
    
    # clear: vacía todo
    d.clear()
    print(d)  # {}
      

    🧠 Acceso a estructuras anidadas (y creación segura)

    Con diccionarios anidados es común el patrón “crear si falta” con setdefault().

    
    inventario = {}
    # Queremos sumar stock en inventario["MAD"]["SKU101"]
    
    ciudad = "MAD"
    sku = "SKU101"
    cantidad = 5
    
    inventario.setdefault(ciudad, {})              # crea dict para ciudad si no existe
    inventario[ciudad][sku] = inventario[ciudad].get(sku, 0) + cantidad
    
    print(inventario)  # {'MAD': {'SKU101': 5}}
      

    Para lecturas anidadas que podrían fallar, combina get():

    
    ciudades = {"MAD": {"poblacion": 3_200_000}}
    poblacion = ciudades.get("BCN", {}).get("poblacion", "N/D")
    print(poblacion)  # N/D
      

    ⚠️ Pitfalls (y cómo evitarlos)

    • Modificar mientras iteras: puede causar errores lógicos.
      
      d = {"a":1,"b":2,"c":3}
      for k in list(d.keys()):    # itera sobre copia
          if d[k] % 2 == 1:
              del d[k]
      print(d)  # {'b': 2}
            
    • ❌ Asumir que popitem() quita “el primero”: desde 3.7+ es LIFO (el último insertado).
    • ❌ Reconstruir diccionarios grandes en bucles críticos (p. ej., a = a | b dentro de un for).
      ✅ Precalcula y fusiona una sola vez.
    • ❌ Usar acceso directo cuando la clave puede faltar.
      .get() o try/except KeyError si necesitas distinguir casos.

    🧩 Patrones prácticos

    1) “Upsert” (insertar o actualizar)

    
    def upsert(d, key, fn_compute):
        d[key] = fn_compute(d.get(key))
        return d[key]
    
    precios = {"pan": 1.1}
    upsert(precios, "pan", lambda v: (v or 0) + 0.2)  # actualiza
    upsert(precios, "leche", lambda v: (v or 0) + 1)  # inserta
    print(precios)  # {'pan': 1.3, 'leche': 1.0}
      

    2) Normalización “crear si falta”

    
    registro = {}
    for linea in ["ES;MAD", "ES;BCN", "PT;LIS"]:
        pais, ciudad = linea.split(";")
        registro.setdefault(pais, []).append(ciudad)
    print(registro)  # {'ES':['MAD','BCN'],'PT':['LIS']}
      

    3) Valores por defecto con setdefault (config)

    
    cfg = {"db": {"host": "localhost"}}
    cfg.setdefault("db", {}).setdefault("port", 5432)
    print(cfg)  # {'db': {'host':'localhost','port':5432}}
      

    📋 Chuleta rápida

    Necesito…UsaEjemplo
    Leer sin error si faltaget()d.get("k","N/D")
    Leer y crear si faltasetdefault()d.setdefault("k", [])
    Actualizar varios camposupdate()d.update({...})
    Fusionar diccionarios| / |=a |= b
    Eliminar y obtener valorpop()d.pop("k", defecto)
    Eliminar último parpopitem()d.popitem()
    Vaciar todoclear()d.clear()

    🧪 Mini-ejercicios

    1. Crea producto = {"sku":101,"stock":5}. Sube el stock +3 con get() sin provocar errores si la clave faltara.
    2. Usa setdefault() para construir un índice de ciudades por país a partir de ["ES;MAD","ES;SEV","PT;LIS"].
    3. Fusiona {"a":1,"b":2} y {"b":99,"c":3} con | y con update(). ¿Diferencias?
    4. Elimina con pop() la clave "email" de un diccionario y captura un valor por defecto si no existía.
    5. Implementa una función limpiar_nulos(d) que borre pares cuya clave o valor sea None sin modificar el diccionario mientras lo iteras.

    🏁 Cierre de la Parte 2

    Ya controlas el ciclo de vida de las entradas de un dict: leer, crear, actualizar y eliminar con seguridad.
    En la Parte 3 veremos cómo iterar profesionalmente por claves, valores y pares con
    keys(), values(), items(), comprensiones y patrones de ordenación.

    🔁 Parte 3 — Iteración y métodos (keys(), values(), items())

    Hasta ahora has aprendido a crear, modificar y eliminar elementos en un dict.
    Ahora vas a recorrerlos de manera eficiente. En Python, los diccionarios permiten
    iterar directamente por sus claves, valores o pares (key-value) con una sintaxis elegante y legible.


    🔹 Iterar por claves

    Cuando recorres un diccionario directamente en un bucle for, obtienes sus claves por defecto.

    
    usuario = {"nombre": "Javi", "edad": 25, "activo": True}
    
    for clave in usuario:
        print(clave)
      

    Salida:

    
    nombre
    edad
    activo
      

    💡 Equivalente a usar for clave in usuario.keys().


    🔹 Iterar por valores

    Usa el método values() para acceder a todos los valores.

    
    for valor in usuario.values():
        print(valor)
      

    Salida:

    
    Javi
    25
    True
      

    values() devuelve una “vista” dinámica del diccionario (tipo dict_values),
    que se actualiza si el diccionario cambia.

    
    v = usuario.values()
    usuario["rol"] = "admin"
    print(v)  # dict_values(['Javi', 25, True, 'admin'])
      

    🔹 Iterar por pares clave-valor

    El método items() devuelve pares (clave, valor) que puedes desempaquetar fácilmente.

    
    for clave, valor in usuario.items():
        print(f"{clave}: {valor}")
      

    Salida:

    
    nombre: Javi
    edad: 25
    activo: True
      

    💡 Este es el método más usado para procesar diccionarios, ya que ofrece acceso completo y legible.


    📋 Tabla comparativa

    MétodoDevuelveEjemploTipo
    d.keys()Claves['nombre','edad','activo']dict_keys
    d.values()Valores['Javi',25,True]dict_values
    d.items()Pares (clave, valor)[('nombre','Javi'),('edad',25)]dict_items

    🔹 Recorrer con condiciones

    Combina iteración y condicionales para filtrar resultados.

    
    usuarios = {
        "Ana": 28,
        "Luis": 19,
        "Sofía": 33,
        "Pedro": 22
    }
    
    print("Usuarios mayores de 25:")
    for nombre, edad in usuarios.items():
        if edad > 25:
            print(f" - {nombre}")
      

    Salida:

    
    Usuarios mayores de 25:
     - Ana
     - Sofía
      

    🧮 Contar elementos o filtrar por valor

    
    # Contar cuántos usuarios tienen edad >= 25
    mayores = sum(1 for e in usuarios.values() if e >= 25)
    print("Mayores de 25:", mayores)
      

    💡 Tip: puedes usar sum() o comprensiones generadoras para escribir bucles más Pythonic.


    🔁 Iterar con índice (enumerate())

    Si necesitas un contador en el bucle, combina enumerate() con items().

    
    for i, (clave, valor) in enumerate(usuarios.items(), start=1):
        print(f"{i}. {clave} → {valor}")
      

    Salida:

    
    1. Ana → 28
    2. Luis → 19
    3. Sofía → 33
    4. Pedro → 22
      

    🧩 Iterar sobre copia de claves (seguridad al modificar)

    Si necesitas eliminar elementos mientras recorres, crea una copia con list(d.keys()) para evitar errores.

    
    edades = {"Ana":28, "Luis":19, "Pedro":17, "Sofía":33}
    
    for nombre in list(edades.keys()):
        if edades[nombre] < 18:
            del edades[nombre]
    
    print(edades)  # {'Ana': 28, 'Luis': 19, 'Sofía': 33}
      

    🔹 Comprensiones de diccionario (dict comprehensions)

    Una forma compacta y elegante de construir nuevos diccionarios a partir de otros.

    
    # Ejemplo: convertir temperaturas °C a °F
    celsius = {"Madrid": 20, "Sevilla": 25, "Bilbao": 18}
    
    fahrenheit = {ciudad: (temp * 9/5) + 32 for ciudad, temp in celsius.items()}
    print(fahrenheit)
      

    Salida:

    
    {'Madrid': 68.0, 'Sevilla': 77.0, 'Bilbao': 64.4}
      

    💡 También puedes aplicar filtros:

    
    # Filtrar solo las ciudades cálidas
    calidas = {c: t for c, t in celsius.items() if t >= 22}
    print(calidas)  # {'Sevilla': 25}
      

    📊 Ejemplo práctico — Procesar inventario

    
    inventario = {
        "Teclado": 12,
        "Ratón": 4,
        "Monitor": 7,
        "Impresora": 2
    }
    
    # Listar productos con stock bajo
    bajos = [p for p, s in inventario.items() if s = 5}
    print(suficiente)
      

    💡 Buena práctica: usar comprensiones mejora claridad y evita bucles innecesarios.


    📋 Comparativa visual de métodos

    Requiere desempaquetarMétodoVentajas
    Nod.keys()Solo claves; ideal para comprobaciones
    Nod.values()Lectura rápida de todos los valores
    d.items()Acceso conjunto clave/valor, más expresivo
    Opcionalenumerate()Índice útil para depuración o impresión numerada

    🧠 Consejos pro de iteración

    • Usa items() en el 90% de los casos: evita accesos extra y mejora legibilidad.
    • Usa dict comprehensions para filtrado o transformación en una línea.
    • Evita modificar el diccionario original mientras lo recorres (usa copia con list()).
    • Recuerda: las vistas keys(), values() e items() se actualizan en vivo.

    🧪 Mini-ejercicios

    1. Crea un diccionario edades y muestra sus claves con for y con keys().
    2. Itera sobre sus valores e imprime solo los mayores de 30.
    3. Usa items() para mostrar “nombre tiene edad años”.
    4. Construye un nuevo diccionario con solo las personas mayores de 25 (usa comprensión).
    5. Usa enumerate() sobre items() para imprimir una lista numerada de pares.

    🏁 Cierre de la Parte 3

    Acabas de dominar los tres métodos esenciales para recorrer un diccionario con precisión quirúrgica:
    keys() para las claves, values() para los datos, y items() para ambos a la vez.
    En la siguiente parte aprenderás cómo anidar diccionarios para modelar estructuras jerárquicas y
    crear configuraciones complejas o inventarios multinivel.

    🏗️ Parte 4 — Diccionarios anidados y ejemplos prácticos

    Un diccionario anidado es un dict dentro de otro dict.
    Se usa para representar estructuras complejas: usuarios con varios atributos, inventarios por ciudad,
    configuraciones de sistemas, etc.


    🔹 Concepto básico

    
    # Diccionario dentro de otro
    usuario = {
        "nombre": "Javier",
        "contacto": {
            "email": "javi@example.com",
            "telefono": "600-111-222"
        },
        "roles": ["admin", "editor"]
    }
    
    print(usuario["contacto"]["email"])  # ➜ javi@example.com
      

    💡 Accedes a los niveles internos encadenando las claves con [].


    📦 Crear diccionarios anidados dinámicamente

    Puedes inicializarlos a mano o construirlos en tiempo real con setdefault().

    
    inventario = {}
    
    # Añadir productos a distintas ciudades
    inventario.setdefault("Madrid", {})["Teclado"] = 15
    inventario.setdefault("Madrid", {})["Ratón"] = 8
    inventario.setdefault("Sevilla", {})["Monitor"] = 5
    
    print(inventario)
    # {'Madrid': {'Teclado': 15, 'Ratón': 8}, 'Sevilla': {'Monitor': 5}}
      

    💡 setdefault() crea automáticamente el diccionario interno si no existía.


    🔍 Lectura segura con get() anidado

    Si no estás seguro de que todas las claves existan, puedes combinar get() para evitar errores.

    
    usuarios = {
        "javi": {"edad": 25, "rol": "admin"},
        "ana": {"edad": 30}
    }
    
    print(usuarios.get("ana", {}).get("rol", "Sin rol"))  # ➜ Sin rol
      

    ✅ Este patrón evita KeyError y es muy usado en código de producción.


    🧠 Modificar valores en diccionarios anidados

    Para cambiar un valor dentro de un nivel interno, navega por las claves:

    
    usuarios["javi"]["rol"] = "editor"
    usuarios["ana"]["rol"] = "colaboradora"
    print(usuarios)
      

    💡 Si no existe el nivel intermedio, primero créalo con setdefault():

    
    usuarios.setdefault("pedro", {}).setdefault("rol", "nuevo")
    print(usuarios)
      

    🧩 Ejemplo práctico 1 — Inventario multinivel

    Un inventario de productos por ciudad y categoría:

    
    inventario = {
        "Madrid": {
            "Periféricos": {"Teclado": 15, "Ratón": 8},
            "Monitores": {"LG 24'": 5}
        },
        "Sevilla": {
            "Periféricos": {"Ratón": 10}
        }
    }
    
    # Acceso
    print(inventario["Madrid"]["Periféricos"]["Teclado"])  # ➜ 15
    
    # Modificación
    inventario["Madrid"]["Monitores"]["Samsung 27'"] = 3
      

    📘 Estructura jerárquica:

    • Nivel 1 → Ciudad
    • Nivel 2 → Categoría
    • Nivel 3 → Producto

    📊 Ejemplo práctico 2 — Configuración de aplicación

    Los archivos de configuración (JSON/YAML) se traducen naturalmente a diccionarios anidados.

    
    config = {
        "servidor": {
            "host": "localhost",
            "puerto": 8080
        },
        "base_datos": {
            "usuario": "root",
            "password": "1234",
            "nombre": "mi_app"
        },
        "log": {
            "nivel": "INFO",
            "ruta": "/var/log/app.log"
        }
    }
    
    print(config["base_datos"]["usuario"])  # ➜ root
      

    ✅ Ideal para sistemas de configuración o aplicaciones web.


    🧮 Ejemplo práctico 3 — Notas de alumnos

    
    alumnos = {
        "Ana": {"Matemáticas": 8.5, "Historia": 9.0},
        "Luis": {"Matemáticas": 7.2, "Historia": 6.5},
        "Sofía": {"Matemáticas": 9.4, "Historia": 8.7}
    }
    
    # Calcular promedios
    for nombre, materias in alumnos.items():
        promedio = sum(materias.values()) / len(materias)
        print(f"{nombre}: promedio = {promedio:.2f}")
      

    💡 Ejemplo real de cómo recorrer y procesar estructuras anidadas con items().


    ⚙️ Ejemplo práctico 4 — API simulada (diccionario de diccionarios)

    
    api = {
        "usuarios": {
            "001": {"nombre": "Ana", "rol": "admin"},
            "002": {"nombre": "Luis", "rol": "editor"}
        },
        "posts": {
            "101": {"titulo": "Introducción a Python", "autor_id": "001"},
            "102": {"titulo": "Estructuras de datos", "autor_id": "002"}
        }
    }
    
    # Buscar el autor del post 102
    autor_id = api["posts"]["102"]["autor_id"]
    autor = api["usuarios"][autor_id]["nombre"]
    print("Autor del post 102:", autor)
      

    💡 Este patrón es idéntico al de bases de datos relacionales o respuestas JSON anidadas.


    🧱 Ejemplo práctico 5 — Fusión de diccionarios anidados

    Para fusionar estructuras con varios niveles, usa update() o el operador | a nivel superior,
    pero escribe una función si quieres combinar niveles internos.

    
    def merge_nested(d1, d2):
        for k, v in d2.items():
            if k in d1 and isinstance(d1[k], dict) and isinstance(v, dict):
                merge_nested(d1[k], v)
            else:
                d1[k] = v
        return d1
    
    a = {"Madrid": {"Ratón": 5}}
    b = {"Madrid": {"Teclado": 10}, "Sevilla": {"Monitor": 3}}
    
    merge_nested(a, b)
    print(a)
    # {'Madrid': {'Ratón': 5, 'Teclado': 10}, 'Sevilla': {'Monitor': 3}}
      

    ✅ Este patrón se usa en frameworks de configuración y librerías como pydantic o OmegaConf.


    📋 Tabla resumen de operaciones anidadas

    OperaciónEjemploResultado
    Acceso directod["nivel1"]["nivel2"]Valor interno
    Acceso segurod.get("nivel1", {}).get("nivel2")Evita KeyError
    Creación si faltad.setdefault("nivel1", {})Crea nivel vacío
    Actualización internad["nivel1"]["nivel2"] = valorModifica un campo interno
    Fusión profundamerge_nested(a,b)Une diccionarios recursivamente

    🧩 Ejemplo práctico 6 — Generar diccionarios anidados por comprensión

    Puedes construir estructuras de varios niveles usando dict comprehensions anidadas.

    
    # Tabla de multiplicar 1-3
    tabla = {i: {j: i*j for j in range(1, 4)} for i in range(1, 4)}
    print(tabla)
    # {1:{1:1,2:2,3:3}, 2:{1:2,2:4,3:6}, 3:{1:3,2:6,3:9}}
      

    💡 Muy usado para generar configuraciones o estructuras jerárquicas automáticamente.


    🧪 Mini-ejercicios

    1. Crea un diccionario empresa que contenga departamentos y empleados con su salario.
    2. Agrega un nuevo departamento si no existe usando setdefault().
    3. Accede de forma segura al salario de un empleado que podría no existir.
    4. Crea una función buscar_producto(inventario, ciudad, nombre) que devuelva el stock o “Sin datos”.
    5. Fusiona dos inventarios anidados con la función merge_nested().

    🏁 Cierre de la Parte 4

    Los diccionarios anidados te permiten modelar datos complejos con jerarquías naturales.
    Ahora puedes representar sistemas completos (usuarios, inventarios, configuraciones, catálogos) en estructuras Python simples y potentes.
    En la Parte 5 verás buenas prácticas, rendimiento y ejercicios integradores para cerrar el tema.

    💡 Parte 5 — Buenas prácticas, rendimiento y ejercicios

    Ya dominas cómo crear, modificar, recorrer y anidar diccionarios.
    Ahora vamos a cerrar el tema con buenas prácticas, optimización y algunos
    ejercicios integradores que consolidarán todo lo aprendido.


    🧭 1. Buenas prácticas de estilo y legibilidad

    • ✅ Usa nombres descriptivos para las claves:
      {"nombre":"Javi","edad":25} es mejor que {"n":"J","e":25}.
    • ✅ Si el contenido representa una entidad (usuario, producto…), nómbralo en singular.
    • ✅ Usa acceso seguro (get(), setdefault()) cuando la clave puede faltar.
    • ✅ Evita anidar más de 3 niveles; usa clases o dataclasses si la jerarquía es profunda.
    • ✅ Formatea tu código: PEP 8 recomienda 4 espacios por nivel y nombres en minúsculas con guiones bajos (snake_case).
    
    # Mal ejemplo
    info = {"a": {"b": {"c": 1}}}
    
    # Mejor
    config_bd = {
        "host": "localhost",
        "puerto": 5432,
        "usuario": "admin"
    }
      

    🧩 2. Buenas prácticas al modificar y fusionar

    • ✅ Evita sobrescribir datos sin verificar la existencia previa de la clave.
    • ✅ Para fusionar diccionarios, usa:
      a | b (crea nuevo) o a |= b (modifica el existente).
    • ✅ Si los diccionarios tienen niveles anidados, usa una función recursiva como merge_nested().
    • ✅ Si vas a eliminar claves durante una iteración, recorre primero una copia con list().
    
    # Ejemplo seguro de fusión
    default_cfg = {"modo":"produccion","debug":False}
    user_cfg = {"debug":True}
    cfg_final = default_cfg | user_cfg
    print(cfg_final)
      

    ⚙️ 3. Rendimiento y eficiencia

    Los diccionarios en Python están optimizados con tablas hash, lo que permite acceso en O(1).
    Sin embargo, hay escenarios donde puedes optimizar aún más.

    OperaciónComplejidadConsejo
    Acceso / inserciónO(1)Usa diccionarios para búsquedas rápidas
    EliminaciónO(1)Evita hacerlo dentro de un bucle de iteración directa
    Fusión (|, update)O(n)Fusiona grandes estructuras una sola vez
    Iteración con items()O(n)Usa items() en lugar de claves/valores separados

    💡 Pro tip: Si tienes muchos accesos repetidos, guarda la referencia local:

    
    # En lugar de esto
    for key in data:
        procesar(data[key])
    
    # Haz esto
    d = data
    for key in d:
        procesar(d[key])
      

    Es más eficiente porque evita búsquedas repetidas en memoria.


    🧮 4. Uso combinado con otras estructuras

    Los diccionarios suelen combinarse con listas y tuplas para representar colecciones de objetos.

    
    usuarios = [
        {"id": 1, "nombre": "Ana", "rol": "admin"},
        {"id": 2, "nombre": "Luis", "rol": "editor"},
        {"id": 3, "nombre": "Sofía", "rol": "viewer"}
    ]
    
    # Obtener nombres de todos los usuarios admin
    admins = [u["nombre"] for u in usuarios if u["rol"] == "admin"]
    print(admins)  # ['Ana']
      

    💡 Este patrón es muy común en desarrollo web, APIs y procesamiento de datos JSON.


    🧰 5. Diccionarios especializados del módulo collections

    Python incluye versiones optimizadas de dict para casos concretos:

    TipoImportaciónUso principal
    defaultdictfrom collections import defaultdictCrea valores por defecto automáticamente
    OrderedDictfrom collections import OrderedDictPreserva orden de inserción (útil antes de Python 3.7)
    Counterfrom collections import CounterCuenta ocurrencias fácilmente
    
    from collections import defaultdict, Counter
    
    # defaultdict
    equipos = defaultdict(list)
    equipos["A"].append("Javi")
    equipos["A"].append("Luis")
    print(equipos)  # defaultdict(list, {'A': ['Javi','Luis']})
    
    # Counter
    letras = Counter("banana")
    print(letras)  # Counter({'a':3, 'n':2, 'b':1})
      

    🧠 6. Antipatrones comunes (y cómo evitarlos)

    • ❌ Acceder a una clave inexistente sin comprobación → KeyError
    • ❌ Usar objetos mutables como claves → TypeError
    • ❌ Iterar y modificar al mismo tiempo → resultados impredecibles
    • ❌ Repetir claves en el literal → se sobrescriben sin aviso
    • ❌ Convertir dicts en listas sin usar .items() correctamente

    ✅ Solución: usar .get(), setdefault() y comprensiones.


    📋 7. Tabla de referencia rápida

    AcciónMétodo o patrónEjemplo
    Leer valord.get(k, defecto)usuario.get("email","N/D")
    Agregar claved[k] = vusuario["rol"]="admin"
    Actualizard.update({...})perfil.update(email="a@b.com")
    Eliminard.pop(k, defecto)d.pop("edad",None)
    Iterar paresfor k,v in d.items()print(k,v)
    FiltrarComprensión{k:v for k,v in d.items() if v>0}
    Fusiónd1 | d2a | b

    🧩 8. Ejercicios integradores

    🔸 Ejercicio 1 — Sistema de notas

    
    notas = {
        "Ana": {"Matemáticas": 9, "Lengua": 7},
        "Luis": {"Matemáticas": 6, "Lengua": 8}
    }
    
    # 1. Añade una nueva asignatura "Inglés" a todos con valor 0.
    # 2. Calcula el promedio total por alumno.
    # 3. Crea un nuevo dict con los promedios.
      

    🔸 Ejercicio 2 — Catálogo de productos

    
    productos = {
        "Teclado": {"precio": 25, "stock": 10},
        "Ratón": {"precio": 15, "stock": 0},
        "Monitor": {"precio": 120, "stock": 5}
    }
    
    # Muestra solo los productos disponibles (stock > 0)
    # Calcula el valor total del inventario
      

    🔸 Ejercicio 3 — Conteo de palabras

    
    texto = "python es poderoso y python es simple"
    palabras = texto.split()
    contador = {}
    
    for p in palabras:
        contador[p] = contador.get(p, 0) + 1
    
    print(contador)
      

    💡 Luego reemplázalo por Counter(palabras) y compara resultados.


    🎯 9. Proyecto final — “Gestor de alumnos”

    Construye un pequeño sistema en Python que:

    • Permita registrar alumnos y asignaturas.
    • Guarde los datos en un diccionario anidado.
    • Permita consultar y modificar calificaciones.
    • Calcule automáticamente el promedio global.
    
    alumnos = {}
    
    while True:
        nombre = input("Nombre del alumno (o salir): ")
        if nombre.lower() == "salir":
            break
        alumnos.setdefault(nombre, {})
        materia = input("Asignatura: ")
        nota = float(input("Nota: "))
        alumnos[nombre][materia] = nota
    
    print("\nListado final:")
    for alumno, materias in alumnos.items():
        promedio = sum(materias.values()) / len(materias)
        print(f"{alumno}: promedio {promedio:.2f}")
      

    💡 Este ejercicio integra lectura, escritura, control de flujo y estructuras anidadas.


    🏁 Conclusión

    Los diccionarios son una de las estructuras más potentes de Python.
    Representan datos reales con flexibilidad, eficiencia y claridad.
    Dominar su sintaxis y patrones te permitirá trabajar con JSON, APIs, bases de datos y estructuras complejas sin esfuerzo.

    Lo que has aprendido:

    • Crear y acceder a pares clave-valor.
    • Modificar y eliminar elementos de forma segura.
    • Recorrer y filtrar con keys(), values() e items().
    • Gestionar estructuras anidadas con setdefault() y get().
    • Aplicar buenas prácticas, optimización y herramientas de collections.

    ¡Enhorabuena! Has completado el estudio de los diccionarios en Python.
    Estás listo para seguir con el siguiente tema: Conjuntos (set) y operaciones de colección.

    Nuestra puntuación
    ¡Haz clic para puntuar esta entrada!
    (Votos: 0 Promedio: 0)
    Scroll al inicio