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 — Introducción y creación de conjuntos (set)

    Objetivo: comprender qué es un conjunto en Python, sus propiedades (unicidad, no orden e inmutabilidad de elementos) y dominar su creación de forma correcta y segura.


    1) ¿Qué es un conjunto (set)?

    Un conjunto es una colección de elementos únicos y no ordenados. Que sean únicos significa que un valor no puede repetirse; si lo intentas, Python lo deduplica automáticamente. Al ser no ordenados, no hay índice ni posición estable para acceder con [i].

    Idea fuerza: “set = bolsa de valores únicos”. Piensa en una lista de asistentes donde cada persona solo aparece una vez.
    # Unicidad: los duplicados desaparecen al crear/el conjunto
    colores = {"rojo", "verde", "azul", "rojo"}
    print(colores)  # {'verde', 'azul', 'rojo'}
    # No orden: el orden interno puede variar entre ejecuciones
    numeros = {3, 1, 4, 1, 5, 9}
    print(numeros)  # p.ej. {1, 3, 4, 5, 9}  (el orden no está garantizado)
    

    Además, los elementos de un conjunto deben ser hashables (inmutables): números, cadenas o tuplas sí; listas o diccionarios, no. Intentar meter un elemento mutable lanza TypeError.

    # Válido: todos los elementos son hashables/inmutables
    ok = {1, "hola", (1, 2)}
    
    # Inválido: lista dentro del set (mutable) → TypeError
    # fallo = {1, [2, 3]}  
    
    Colección¿Ordenada?¿Permite duplicados?Mutabilidad del contenedorElementos requeridos
    listMutableCualquiera
    tupleInmutableCualquiera
    setNo (sin índice)No (únicos)MutableHashables

    2) Cómo crear conjuntos correctamente

    Hay dos formas habituales de crear un set en Python:

    1. Usando llaves {} con elementos literales.
    2. Usando la función set(iterable) para construir desde otra colección.
    # 1) Literal con llaves
    colores = {"rojo", "verde", "azul", "rojo"}   # 'rojo' repetido → se deduplica
    print(colores)  # {'verde', 'azul', 'rojo'}
    
    # 2) A partir de un iterable (lista, tupla, etc.)
    numeros = set([1, 2, 3, 2, 1])
    print(numeros)  # {1, 2, 3}
    

    Ambos ejemplos demuestran la eliminación automática de duplicados al construir el conjunto.

    ¡Ojo, trampa clásica! {} no crea un conjunto vacío, sino un diccionario vacío. Para un set vacío usa set().

    vacio_mal = {}
    print(type(vacio_mal))   # <class 'dict'>  ← Diccionario, no set
    
    vacio_bien = set()
    print(type(vacio_bien))  # <class 'set'>   ← Conjunto vacío correcto
    

    3) ¿Cuándo usar conjuntos?

    • Eliminar duplicados de una lista rápidamente.
    • Comprobar pertenencia (x in set) de forma muy eficiente incluso con muchos elementos.
    • Operaciones de teoría de conjuntos: unión, intersección, diferencia, etc. (las verás en Parte 3).
    # Deduplicar rápido (sin mantener orden original)
    datos = [3, 1, 4, 1, 5, 9, 2, 6, 5]
    unicos = set(datos)      # {1,2,3,4,5,6,9}
    print(unicos)
    
    # Pertenencia: O(1) promedio
    frutas = {"manzana", "naranja", "plátano"}
    print("manzana" in frutas)  # True
    print("uva" in frutas)      # False
    

    La comprobación de pertenencia en conjuntos es muy rápida gracias a su implementación con tablas hash.


    4) Limitaciones y buenas prácticas desde el día 1

    • No hay indexación: no puedes hacer mi_set[0] (usa bucles o convierte a lista si necesitas índices).
    • El orden no está garantizado: perfecto para colecciones, no para secuencias ordenadas.
    • Solo elementos hashables: nada de listas/dicts dentro del set.
    • Creación del set vacío: siempre set(), nunca {}.

    5) Mini-prácticas (check rápido)

    1. Crea un set a partir de ["a", "b", "a", "c", "c"] e imprime su tamaño.
      letras = set(["a", "b", "a", "c", "c"])
      print(len(letras))  # ¿Resultado?
    2. Intenta añadir una lista [1,2] al set {1, (2,3)}. ¿Qué error obtienes? ¿Por qué?
      s = {1, (2, 3)}
      # s.add([1, 2])  # Descomenta y ejecuta → TypeError: unhashable type: 'list'
    3. Crea un set vacío de la forma correcta y verifica su tipo.
      v = set()
      print(type(v))

    6) Resumen ejecutivo

    • set = colección de elementos únicos, no ordenada, sin índice.
    • Creación: literal {"a","b"} o función set(iterable); el vacío es set(), no {}.
    • Elementos deben ser hashables (números, str, tuplas…).
    • Casos de uso: deduplicación, pertenencia rápida y operaciones de teoría de conjuntos.
    En la Parte 2 veremos eliminación de duplicados desde otras estructuras, operaciones básicas (añadir, quitar, consultar) y patrones iniciales.

    Parte 2 — Eliminación de duplicados y operaciones básicas

    Objetivo: dominar la deduplicación con set y aplicar con solvencia las operaciones básicas: añadir, eliminar, consultar, iterar y convertir entre tipos.


    1) Eliminar duplicados desde otras colecciones

    La forma más directa de eliminar duplicados de una lista/tupla es construir un set desde ese iterable. Recuerda: los conjuntos garantizan unicidad y no mantienen orden. Si después necesitas indexar, conviértelo de vuelta a lista.

    # Deduplicación rápida (no preserva el orden original)
    datos = [3, 1, 4, 1, 5, 9, 2, 6, 5]
    unicos = set(datos)         # {1, 2, 3, 4, 5, 6, 9}
    print(unicos)
    
    # Si necesitas índice, vuelve a lista (el orden resultante es arbitrario)
    unicos_lista = list(unicos)
    print(unicos_lista)
    
    Tip: Si también quieres preservar el orden de aparición original, deduplica con un diccionario intermedio:

    def dedupe_preservando_orden(seq):
        return list(dict.fromkeys(seq))
    
    print(dedupe_preservando_orden(datos))  # [3, 1, 4, 5, 9, 2, 6]

    2) Añadir elementos: add() y update()

    add(x) agrega un único elemento; update(iterable) agrega varios (desde cualquier iterable o incluso varios iterables). Si el elemento ya existe, no pasa nada porque la colección es única.

    tecnologias = {"Python", "JavaScript", "SQL"}
    tecnologias.add("Java")                 # Un elemento
    print(tecnologias)  # {'Python', 'JavaScript', 'SQL', 'Java'}
    
    nuevos = ["Go", "Rust", "TypeScript"]
    tecnologias.update(nuevos)              # Varios elementos
    print(tecnologias)  # {'Python', 'JavaScript', 'SQL', 'Java', 'Go', 'Rust', 'TypeScript'}
    
    tecnologias.add("Python")               # Ya existía → sin efecto
    print(tecnologias)
    
    OperaciónFirmaEfectoIn-place
    addset.add(elem)Inserta un único elemento si no estaba.
    updateset.update(*iterables)Inserta varios elementos de uno o más iterables.

    3) Eliminar elementos: remove(), discard(), pop(), clear()

    Eliminar en sets es directo, con diferencias sutiles:

    • remove(x): borra x; si no existe, lanza KeyError.
    • discard(x): borra x si existe; no lanza error si no está.
    • pop(): extrae y devuelve un elemento arbitrario.
    • clear(): deja el set vacío.

    Todos operan in-place.

    frutas = {"manzana", "naranja", "plátano"}
    
    frutas.remove("naranja")
    print(frutas)  # {'manzana', 'plátano'}
    
    frutas.discard("uva")      # No existe → no rompe
    print(frutas)
    
    item = frutas.pop()        # Quita un elemento cualquiera
    print("Eliminado:", item, " → ahora:", frutas)
    
    frutas.clear()
    print(frutas)              # set()
    
    Antipatrón común: hacer remove(x) sin garantizar que x exista. Mitigación: calcula if x in s: s.remove(x) o usa directamente discard(x).

    4) Consultas básicas: len(), pertenencia in, copy()

    Los sets dan consultas ágiles: len(s) cuenta elementos; x in s es muy eficiente por tabla hash; copy() clona superficialmente el conjunto.

    planetas = {"Mercurio", "Venus", "Tierra", "Marte"}
    print(len(planetas))       # 4
    
    print("Saturno" in planetas)  # False
    print("Tierra" in planetas)   # True
    
    copia = planetas.copy()
    copia.add("Júpiter")
    print(planetas)            # {'Mercurio', 'Venus', 'Tierra', 'Marte'}
    print(copia)               # incluye 'Júpiter'
    

    5) Iteración sobre conjuntos

    Itera con for como con cualquier colección. Recuerda: el orden de recorrido es arbitrario (no hay índice).

    ciudades = {"Madrid", "Barcelona", "Valencia", "Sevilla"}
    for c in ciudades:
        print("Visitando", c)
    

    6) Conversión entre tipos

    Muy común pasar lista → set para deduplicar y luego set → lista para indexar.

    lista = [1, 2, 2, 3, 4, 4, 5]
    sin_dupes = set(lista)   # {1, 2, 3, 4, 5}
    lista_indexable = list(sin_dupes)
    print(lista_indexable)
    

    7) Relaciones rápidas: subconjunto y superconjunto

    Para relaciones entre conjuntos: A.issubset(B) (A ⊆ B) y A.issuperset(B) (A ⊇ B). Ideales para validaciones y permisos.

    pares = {2, 4, 6, 8}
    todos = {1, 2, 3, 4, 5, 6, 7, 8}
    print(pares.issubset(todos))   # True
    
    frutas = {"manzana", "naranja", "plátano", "fresa", "kiwi"}
    tropicales = {"plátano", "kiwi"}
    print(frutas.issuperset(tropicales))  # True
    

    8) Caso práctico exprés: asistencia a un evento

    Aplica varias operaciones básicas en una mini-situación real.

    # Registro de asistentes por día
    dia1 = {"Ana", "Carlos", "Elena", "David", "Beatriz"}
    dia2 = {"Carlos", "Elena", "Fernando", "Gabriela"}
    
    # Alta de última hora
    dia1.add("Héctor")
    
    # Quien vino ambos días (intersección)
    ambos = dia1.intersection(dia2)
    
    # Cancelación (sin romper si no está)
    dia1.discard("David")
    
    # ¿Todos los del día 2 repiten con día 1?
    todos_repiten = dia2.issubset(dia1)
    
    # Total de únicos (unión)
    total_unicos = len(dia1.union(dia2))
    
    print("Día 1:", dia1)
    print("Ambos días:", ambos)
    print("¿Todos día2 en día1?:", todos_repiten)
    print("Total únicos:", total_unicos)
    

    9) Mini-retos (5′

    1. Dado emails = ["a@x", "b@x", "a@x", "c@x", "b@x"], imprime cuántos emails únicos hay y una lista indexable con ellos.
      emails = ["a@x", "b@x", "a@x", "c@x", "b@x"]
      unicos = set(emails)
      print(len(unicos))
      print(list(unicos))
    2. Implementa borrado seguro de un elemento x en s sin lanzar error y registra si realmente se eliminó.
      def safe_discard(s, x):
          existed = x in s
          s.discard(x)
          return existed
    3. Crea una función que reciba una lista y devuelva otra lista sin duplicados y con el orden original.
      def dedupe_stable(seq):
          return list(dict.fromkeys(seq))

    10) Resumen ejecutivo

    • Deduplicación: set(iterable) elimina duplicados; si necesitas índice, convierte a list.
    • Añadir: add() (uno), update() (varios). In-place.
    • Eliminar: remove() (error si no existe), discard() (seguro), pop() (arbitrario), clear() (vacía).
    • Consultas: len, pertenencia in (muy eficiente), copy().
    • Iteración: sin orden garantizado; no hay indexación.
    En la Parte 3 entraremos en las operaciones matemáticas entre conjuntos: unión, intersección y diferencia, con equivalentes en operadores (|, &, -, ^).

    Parte 3 — Operaciones matemáticas: unión, intersección y diferencia

    Objetivo: dominar las operaciones de teoría de conjuntos en Python con sus dos estilos equivalentes: métodos y operadores, y saber cuándo usar cada uno.


    0) Dos formas de decir lo mismo

    Python ofrece métodos (union(), intersection(), difference()) y operadores (|, &, -) para expresar las mismas operaciones. Usa el que te resulte más legible: rendimiento equivalente.

    OperaciónMétodoOperadorResultado
    UniónA.union(B)A | BTodos los elementos de A y B (sin duplicados)
    IntersecciónA.intersection(B)A & BSolo los elementos comunes
    DiferenciaA.difference(B)A - BElementos de A que no están en B

    1) Unión: juntar sin duplicados

    Equivalencias: A.union(B, C, ...)A | B | C. Devuelve un nuevo set.

    eu = {"Madrid", "París", "Roma", "Berlín"}
    asia = {"Tokio", "Pekín", "Seúl", "Bangkok"}
    
    # Método
    todas = eu.union(asia)
    print(todas)
    
    # Operador
    todas2 = eu | asia
    print(todas2)  # Equivalente
    
    Pro tip: puedes unir múltiples conjuntos a la vez: A.union(B, C) o A | B | C.

    2) Intersección: lo que tienen en común

    Equivalencias: A.intersection(B)A & B. Devuelve los elementos comunes.

    mates = {"Ana", "Carlos", "Elena", "David"}
    fisica = {"Carlos", "Elena", "Fernando", "Gabriela"}
    
    # Método
    ambas = mates.intersection(fisica)
    print(ambas)  # {'Carlos', 'Elena'}
    
    # Operador
    ambas2 = mates & fisica
    print(ambas2)  # Equivalente
    

    Patrón real: “quién asistió ambos días”.

    dia1 = {"Ana", "Carlos", "Elena", "David", "Beatriz"}
    dia2 = {"Carlos", "Elena", "Fernando", "Gabriela"}
    ambos_dias = dia1 & dia2
    print(ambos_dias)  # {'Carlos', 'Elena'} 

    3) Diferencia: lo que queda en A quitando B

    Equivalencias: A.difference(B)A - B. Devuelve elementos exclusivos de A.

    ingredientes = {"harina", "huevos", "azúcar", "leche", "mantequilla"}
    usados = {"harina", "huevos", "azúcar"}
    
    # Método
    restantes = ingredientes.difference(usados)
    print(restantes)  # {'leche', 'mantequilla'}
    
    # Operador
    restantes2 = ingredientes - usados
    print(restantes2)  # Equivalente
    

    Otro caso típico: “solo en A, no en B”.

    grupo_a = {"Ana", "Carlos", "Elena", "David"}
    grupo_b = {"Elena", "Fernando", "Gabriela", "Carlos"}
    solo_a = grupo_a - grupo_b
    print(solo_a)  # {'Ana', 'David'}

    4) Encadenar operaciones (combinaciones útiles)

    Combina operadores como en álgebra de conjuntos: paréntesis para clarificar y listo. Equivalente a encadenar métodos.

    A = {1, 2, 3, 4}
    B = {3, 4, 5, 6}
    C = {5, 6, 7, 8}
    
    # Elementos en A y B, pero no en C
    res = (A & B) - C
    print(res)  # {3, 4}
    
    # Unión y diferencia en cadena (equivalente a métodos)
    res2 = (A | C) - B
    print(res2)  # {1, 2, 7, 8}
    
    Lectura limpia: usa operadores en cálculos cortos y expresivos; prefiere métodos cuando pases múltiples conjuntos o necesites nombres autoexplicativos (p. ej. union(t1, t2, t3)).

    5) Mini-prácticas

    1. Dadas dos listas con duplicados, muestra la unión de emails únicos:
      a = {"a@x", "b@x", "c@x"}
      b = {"b@x", "d@x"}
      print(a | b)                 # Unión
      print(a.union(b))            # Equivalente
    2. Dado el gusto de dos usuarios, muestra los géneros que comparten (intersección) y los exclusivos del primero (diferencia):
      u1 = {"acción", "comedia", "ciencia ficción", "aventura"}
      u2 = {"drama", "comedia", "romance", "documental"}
      print(u1 & u2)   # comunes
      print(u1 - u2)   # solo u1
    3. Conjuntos A, B, C, calcula (A & B) - C usando métodos:
      res = A.intersection(B).difference(C)
      print(res)

    6) Resumen ejecutivo

    • Unión = todo sin duplicados (union|).
    • Intersección = solo lo común (intersection&).
    • Diferencia = lo que está en A y no en B (difference-).
    • Métodos vs operadores: mismos resultados y rendimiento; elige por legibilidad.
    En la Parte 4 veremos métodos comunes que modifican en sitio (add, discard, update) y un caso práctico más grande.

    Parte 4 — Métodos comunes (add, discard, update) y ejemplos prácticos

    Objetivo: dominar la mutación segura de conjuntos con add, discard y update, entendiendo sus matices, anti-patrones y usos reales.


    1) Añadir elementos

    add(x) inserta un único elemento si no existía; update(iterable) inserta varios elementos desde uno o más iterables. Si ya estaban, no pasa nada (la colección es única).

    # add: un solo elemento (idempotente por unicidad)
    tecs = {"Python", "JavaScript", "SQL"}
    tecs.add("Java")
    print(tecs)  # {'Python', 'JavaScript', 'SQL', 'Java'}
    
    # update: varios elementos desde cualquier iterable
    nuevos = ["Go", "Rust", "TypeScript"]
    tecs.update(nuevos)
    print(tecs)  # {'Python', 'JavaScript', 'SQL', 'Java', 'Go', 'Rust', 'TypeScript'}
    
    # Intentar añadir un duplicado no cambia el conjunto
    nums = {1, 2, 3}
    nums.add(2)
    print(nums)  # {1, 2, 3}
    

    Ref.: creación y update con iterables; idempotencia por unicidad.

    Pro tip: update() acepta varios iterables: s.update(lista, otro_set, tupla).

    2) Eliminar elementos de forma segura

    Para borrar un elemento concreto usa:

    • discard(x)no lanza error si x no está (seguro).
    • remove(x) — lanza KeyError si x no existe.
    animales = {"perro", "gato", "conejo"}
    
    # Borrado seguro (no rompe si no existe)
    animales.discard("pájaro")
    print(animales)  # {'perro', 'gato', 'conejo'}
    
    # Borrado estricto (rompe si no existe)
    # animales.remove("pájaro")  # KeyError
    

    Ref.: diferencias remove vs discard.

    Otros borrados útiles:

    • pop(): extrae y devuelve un elemento arbitrario (no hay orden).
    • clear(): vacía el conjunto.
    colores = {"rojo", "verde", "azul"}
    quitado = colores.pop()   # elemento cualquiera
    print("Se eliminó:", quitado, "| ahora:", colores)
    
    nums = {1, 2, 3, 4}
    nums.clear()
    print(nums)  # set()
    

    Ref.: pop y clear en operaciones básicas.

    Antipatrón: invocar remove(x) sin comprobar pertenencia — mejor discard(x) o if x in s: s.remove(x).

    3) Actualizaciones masivas del propio conjunto

    Cuando quieres modificar en sitio (no crear un nuevo set) con operaciones entre conjuntos, utiliza las versiones update:

    • intersection_update(S): deja solo elementos comunes con S.
    • difference_update(S): elimina del actual los elementos presentes en S.
    • symmetric_difference_update(S): deja los que están en uno u otro, pero no en ambos.
    • update(S): añade los elementos de S (equivalente a una unión in-place).
    # intersection_update: filtra a los que cumplen dos condiciones
    activos = {"user1", "user2", "user3", "user4"}
    premium = {"user2", "user4", "user5"}
    activos.intersection_update(premium)
    print(activos)  # {'user2', 'user4'}
    
    # difference_update: quita los "ya tratados"
    todos = {"Python", "Java", "SQL", "JavaScript", "C++"}
    completados = {"Python", "SQL"}
    todos.difference_update(completados)
    print(todos)  # {'Java', 'JavaScript', 'C++'}
    
    # symmetric_difference_update: deja solo los no coincidentes
    g1 = {"Ana", "Carlos", "David"}
    g2 = {"Carlos", "Elena", "Fernando"}
    g1.symmetric_difference_update(g2)
    print(g1)  # {'Ana', 'David', 'Elena', 'Fernando'}
    
    # update: unión en sitio
    frutas_locales = {"manzana", "pera", "naranja"}
    frutas_importadas = {"piña", "mango", "kiwi"}
    frutas_locales.update(frutas_importadas)
    print(frutas_locales)
    

    Ref.: familia update in-place para intersección, diferencia, diferencia simétrica y unión.


    4) Ejemplos prácticos (listos para pegar)

    4.1 Registro de asistencia (alta, baja, conteo únicos)

    # Día 1 y Día 2
    dia1 = {"Ana", "Carlos", "Elena", "David", "Beatriz"}
    dia2 = {"Carlos", "Elena", "Fernando", "Gabriela"}
    
    # Alta de última hora
    dia1.add("Héctor")
    
    # Quien vino ambos días
    ambos = dia1.intersection(dia2)
    
    # Borrado seguro (cancelación)
    dia1.discard("David")
    
    # ¿Todos los del día 2 están en el día 1?
    todos_repiten = dia2.issubset(dia1)
    
    # Total de asistentes únicos (unión)
    total_unicos = len(dia1.union(dia2))
    
    print("Día 1:", dia1)
    print("Ambos días:", ambos)
    print("¿Todos día2 en día1?:", todos_repiten)
    print("Total únicos:", total_unicos)
    

    Ref.: caso práctico de asistencia con add, discard, intersection, issubset y union.

    4.2 Inventario: altas, bajas “blandas” y sincronización

    stock = {"laptop", "teléfono", "tablet", "auriculares"}
    vendidos = {"laptop", "auriculares"}
    
    # Quitar vendidos (sin crear un set nuevo)
    stock.difference_update(vendidos)
    print("Disponibles:", stock)  # {'teléfono', 'tablet'}
    
    # Llegan nuevos productos
    nueva_llegada = {"monitor", "tablet"}  # 'tablet' ya estaba
    stock.update(nueva_llegada)
    print("Tras llegada:", stock)          # {'teléfono', 'tablet', 'monitor'}
    
    # Baja blanda de un artículo que puede no existir
    def baja_segura(s, item):
        existed = item in s
        s.discard(item)
        return existed
    
    print("Borrado 'mouse':", baja_segura(stock, "mouse"))  # False, pero no rompe
    

    Ref.: difference_update y update para sincronizar inventario; discard para borrado tolerante.

    4.3 Etiquetado de usuarios con filtros acumulativos (in-place)

    usuarios = {"u1", "u2", "u3", "u4", "u5"}
    activos = {"u1", "u2", "u4", "u6"}
    premium = {"u2", "u5"}
    
    # Qué usuarios quedan tras aplicar condición "activos"
    usuarios.intersection_update(activos)
    # Ahora, añade etiqueta "premium" (unión)
    usuarios.update(premium)
    print(usuarios)  # {'u1', 'u2', 'u4', 'u5'}
    

    Ref.: patrón de filtrado incremental con intersection_update y enriquecimiento con update.


    5) Buenas prácticas rápidas

    • Borrado seguro por defecto: prefiere discard a remove salvo que quieras detectar la ausencia con excepción.
    • Mutaciones en lote: usa los sufijos _update (intersection_update, difference_update, etc.) para evitar crear objetos intermedios.
    • Coste de pertenencia: x in s es muy eficiente (tablas hash), ideal para validaciones y filtros.
    • Sin orden ni índice: si necesitas orden estable o posiciones, convierte a lista o usa otra estructura.

    6) Micro-retos

    1. Implementa agrega_unicos(s, *iterables) que inserte elementos de múltiples iterables usando update.
      def agrega_unicos(s, *iterables):
          s.update(*iterables)
          return s
    2. Escribe baja_segura(s, x) que devuelva True si existía y se borró, False si no.
      def baja_segura(s, x):
          existe = x in s
          s.discard(x)
          return existe
    3. Dado pendientes y hechos (conjuntos), aplica difference_update y luego añade extra con update.
      pendientes = {"tarea1", "tarea2", "tarea3"}
      hechos = {"tarea2"}
      extra = {"tarea4"}
      pendientes.difference_update(hechos)
      pendientes.update(extra)
      print(pendientes)
    En la Parte 5 cerramos con buenas prácticas, rendimiento (por qué los sets son veloces en pertenencia/operaciones) y una batería de ejercicios finales para evaluación.

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

    Objetivo: aplicar criterios profesionales al trabajar con set, entender el rendimiento bajo el capó y validar el aprendizaje con ejercicios prácticos.


    1) Buenas prácticas (do’s & don’ts)

    1.1 Creación correcta y datos válidos

    • Set vacío: usa set(), no {} (eso crea un diccionario).
    • Elementos hashables únicamente (números, str, tuplas…). No metas listas ni diccionarios.
    • Deduplicación: set(iterable) elimina duplicados de forma directa y expresiva.
    # Correctos
    vacio = set()
    ok = {1, "a", (2, 3)}
    
    # Incorrectos (elementos mutables)
    # mal = { [1,2], {"k":"v"} }  # TypeError
    

    1.2 Mutación segura y expresiva

    • Inserciones: add(x) para uno; update(*iterables) para “lotes”.
    • Borrado seguro: prioriza discard(x) frente a remove(x) (evitas KeyError).
    • In-place masivo: usa intersection_update, difference_update, symmetric_difference_update, update para evitar objetos intermedios.
    # Borrado tolerante a ausencia
    def baja_segura(s, x):
        existed = x in s
        s.discard(x)    # no lanza excepción
        return existed
    

    1.3 Operadores vs. métodos

    • Mismo rendimiento; elige por legibilidad. Operadores para expresiones cortas; métodos cuando pasas múltiples conjuntos o quieres semántica explícita.
    # Equivalentes
    r1 = A.union(B).difference(C)
    r2 = (A | B) - C
    

    1.4 Orden, indexación y conversiones

    • Sin orden ni índice: no uses s[i]. Para indexar u ordenar, convierte a list.
    • Copias: usa copy() para clonar; la asignación simple referencia el mismo objeto.
    # Indexar → primero convierto
    s = {"a", "b", "c"}
    lst = list(s)  # orden arbitrario
    

    2) Rendimiento: lo importante para producción

    • Pertenencia (x in s) y muchas operaciones clave son muy eficientes gracias a tablas hash.
    • Intersección/Unión/Diferencia están optimizadas; trabaja in-place si procesas grandes volúmenes para reducir GC y picos de memoria.
    • Operadores vs. métodos: equivalencia en rendimiento (los operadores llaman a los métodos). Decide por estilo/claridad.
    # Micro-benchmark orientativo (idea de patrón, no cifras absolutas):
    import time
    
    A = set(range(100_000))
    B = set(range(50_000, 150_000))
    
    t0 = time.time()
    _ = A & B    # intersección (operador)
    t1 = time.time()
    _ = A.intersection(B)  # intersección (método)
    t2 = time.time()
    
    print("Operador &:", (t1 - t0)*1000, "ms")
    print("Método intersection:", (t2 - t1)*1000, "ms")
    # En la práctica, tiempos similares.
    
    Regla de oro: primero escribe legible, luego optimiza puntos calientes. Los set ya te dan un baseline de rendimiento muy sólido.

    3) Checklist de calidad (rápido)

    • ¿Usas set() para vacíos y deduplicación? ✔️
    • ¿Evitas remove salvo que quieras capturar la ausencia? ✔️
    • ¿Aplicas _update para mutaciones masivas? ✔️
    • ¿Conviertes a lista antes de indexar/ordenar? ✔️
    • ¿Eliges operadores o métodos por legibilidad/contexto? ✔️

    4) Ejercicios finales

    4.1 Deduplicación con preservación de orden

    Enunciado: Implementa dedupe_stable(seq) que devuelva una lista sin duplicados manteniendo el orden de aparición.

    def dedupe_stable(seq):
        return list(dict.fromkeys(seq))  # patrón clásico
    

    Tip: set elimina duplicados pero no preserva orden; usa dict.fromkeys si el orden importa.

    4.2 Normalización de inventario

    Enunciado: Dado stock y vendidos (sets), elimina vendidos in-place y añade nueva_llegada sin duplicados.

    def normaliza(stock, vendidos, nueva_llegada):
        stock.difference_update(vendidos)
        stock.update(nueva_llegada)
        return stock
    

    Aplica difference_update y update para eficiencia y claridad.

    4.3 Filtrado incremental de usuarios

    Enunciado: Parte de usuarios, deja solo los activos e incorpora premium (unión) sin crear sets temporales.

    def filtra_y_enriquece(usuarios, activos, premium):
        usuarios.intersection_update(activos)
        usuarios.update(premium)
        return usuarios
    

    Intersección y unión in-place para pipelines sencillos.

    4.4 Auditoría de permisos

    Enunciado: Comprueba si permisos_requeridos ⊆ permisos_usuario y devuelve faltantes.

    def faltantes(permisos_usuario, permisos_requeridos):
        if permisos_requeridos.issubset(permisos_usuario):
            return set()
        return permisos_requeridos - permisos_usuario
    

    Subconjuntos y diferencia: validación típica de scopes/roles.

    4.5 Preferencias de contenidos (expresión compuesta)

    Enunciado: Dados A, B, C (sets), calcula “lo que está en A ∩ B y no en C” de dos formas equivalentes.

    def expr(A, B, C):
        via_metodos = A.intersection(B).difference(C)
        via_operadores = (A & B) - C
        return via_metodos, via_operadores
    

    Equivalencia operadores↔métodos en resultados y rendimiento.


    5) Soluciones (resumen)

    • 4.1 dict.fromkeys para orden estable; set si solo importa unicidad.
    • 4.2 difference_update + update (mutación en sitio, menos memoria).
    • 4.3 intersection_update + update (pipeline declarativo).
    • 4.4 issubset/- para auditorías de permisos.
    • 4.5 intersection/difference&/- (equivalentes).
    Nuestra puntuación
    ¡Haz clic para puntuar esta entrada!
    (Votos: 0 Promedio: 0)
    Scroll al inicio