diff --git a/lab-python-error-handling.ipynb b/lab-python-error-handling.ipynb index 3e50ef8..656875f 100644 --- a/lab-python-error-handling.ipynb +++ b/lab-python-error-handling.ipynb @@ -44,15 +44,265 @@ "execution_count": null, "id": "cc2c441d-9dcf-4817-b097-cf6cbe440846", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=== Inicialización del inventario ===\n", + "\n", + "=== Pedidos del cliente ===\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n", + "Error: Producto inválido. Elige entre: t-shirt, mug, hat, book, keychain.\n" + ] + } + ], "source": [ - "# your code goes here" + "import re\n", + "import string\n", + "from typing import Iterable, List, Dict, Tuple, Callable\n", + "\n", + "# ==========================\n", + "# Utilidades y validaciones\n", + "# ==========================\n", + "\n", + "class InputTypeError(TypeError):\n", + " pass\n", + "\n", + "class MathDomainError(ValueError):\n", + " pass\n", + "\n", + "def assert_iterable_not_string(value, name=\"value\"):\n", + " if isinstance(value, (str, bytes)):\n", + " raise InputTypeError(f\"'{name}' no puede ser str/bytes; se esperaba una colección de elementos.\")\n", + " try:\n", + " iter(value)\n", + " except TypeError:\n", + " raise InputTypeError(f\"'{name}' debe ser iterable (lista, tupla, set, etc.).\")\n", + "\n", + "# =========================================\n", + "# 1) get_unique_list: únicos preservando orden\n", + "# =========================================\n", + "def get_unique_list(items: Iterable) -> List:\n", + " \"\"\"\n", + " Devuelve los elementos únicos preservando el orden de aparición.\n", + " Maneja entradas no iterables / strings con raise y mensajes claros.\n", + " \"\"\"\n", + " try:\n", + " assert_iterable_not_string(items, \"items\")\n", + " vistos = set()\n", + " resultado = []\n", + " for x in items:\n", + " if x not in vistos:\n", + " resultado.append(x)\n", + " vistos.add(x)\n", + " except InputTypeError as e:\n", + " print(f\"Error: {e}\")\n", + " raise\n", + " else:\n", + " return resultado\n", + " finally:\n", + " # Punto de trazabilidad (opcional)\n", + " pass\n", + "\n", + "# =========================================\n", + "# 2) count_case: conteo de mayúsculas/minúsculas\n", + "# =========================================\n", + "def count_case(texto: str) -> Dict[str, int]:\n", + " \"\"\"\n", + " Cuenta letras mayúsculas y minúsculas. Ignora no letras.\n", + " \"\"\"\n", + " if not isinstance(texto, str):\n", + " raise InputTypeError(\"count_case espera un str como entrada.\")\n", + " try:\n", + " upper = sum(1 for c in texto if c.isalpha() and c.isupper())\n", + " lower = sum(1 for c in texto if c.isalpha() and c.islower())\n", + " except Exception as e:\n", + " print(f\"Error inesperado en count_case: {e}\")\n", + " raise\n", + " else:\n", + " return {\"upper\": upper, \"lower\": lower}\n", + " finally:\n", + " pass\n", + "\n", + "# =========================================\n", + "# 3) remove_punctuation: elimina puntuación\n", + "# =========================================\n", + "_PUNCT_TABLE = str.maketrans(\"\", \"\", string.punctuation)\n", + "\n", + "def remove_punctuation(texto: str, keep: str = \"\") -> str:\n", + " \"\"\"\n", + " Elimina puntuación estándar. Si 'keep' incluye caracteres, estos se conservan.\n", + " \"\"\"\n", + " if not isinstance(texto, str):\n", + " raise InputTypeError(\"remove_punctuation espera un str.\")\n", + " try:\n", + " if keep:\n", + " # Construir una clase de regex que quite toda puntuación excepto los 'keep'\n", + " keep_escaped = re.escape(keep)\n", + " pattern = rf\"[{re.escape(string.punctuation).replace(keep_escaped, '')}]\"\n", + " return re.sub(pattern, \"\", texto)\n", + " # vía tabla rápida\n", + " return texto.translate(_PUNCT_TABLE)\n", + " except Exception as e:\n", + " print(f\"Error en remove_punctuation: {e}\")\n", + " raise\n", + " finally:\n", + " pass\n", + "\n", + "# =========================================\n", + "# 4) word_count: cuenta palabras (normalizado)\n", + "# =========================================\n", + "def word_count(texto: str, lowercase: bool = True, remove_punct: bool = True) -> Dict[str, int]:\n", + " \"\"\"\n", + " Cuenta frecuencia de palabras. Normaliza espacios; opcionalmente quita puntuación y pasa a minúsculas.\n", + " \"\"\"\n", + " if not isinstance(texto, str):\n", + " raise InputTypeError(\"word_count espera un str.\")\n", + " try:\n", + " if remove_punct:\n", + " texto = remove_punctuation(texto)\n", + " if lowercase:\n", + " texto = texto.lower()\n", + " # separa por cualquier espacio\n", + " palabras = re.findall(r\"\\S+\", texto)\n", + " freqs: Dict[str, int] = {}\n", + " for w in palabras:\n", + " freqs[w] = freqs.get(w, 0) + 1\n", + " except Exception as e:\n", + " print(f\"Error en word_count: {e}\")\n", + " raise\n", + " else:\n", + " return freqs\n", + " finally:\n", + " pass\n", + "\n", + "# =========================================\n", + "# 5) Calculadora con operaciones múltiples y recursividad\n", + "# =========================================\n", + "\n", + "def factorial(n: int) -> int:\n", + " \"\"\"Recursivo: factorial con validación.\"\"\"\n", + " if n < 0:\n", + " raise MathDomainError(\"factorial no definido para números negativos.\")\n", + " if n in (0, 1):\n", + " return 1\n", + " return n * factorial(n - 1)\n", + "\n", + "def potencia(base: float, exp: int) -> float:\n", + " \"\"\"Recursivo (exponente entero).\"\"\"\n", + " if exp == 0:\n", + " return 1.0\n", + " if exp < 0:\n", + " # a^(−n) = 1/a^n\n", + " return 1.0 / potencia(base, -exp)\n", + " # exp > 0\n", + " return base * potencia(base, exp - 1)\n", + "\n", + "def mcd(a: int, b: int) -> int:\n", + " \"\"\"Recursivo: máximo común divisor (Euclides).\"\"\"\n", + " a, b = abs(a), abs(b)\n", + " if b == 0:\n", + " return a\n", + " return mcd(b, a % b)\n", + "\n", + "Operation = Callable[..., float]\n", + "\n", + "def safe_div(a: float, b: float) -> float:\n", + " if b == 0:\n", + " raise MathDomainError(\"División por cero.\")\n", + " return a / b\n", + "\n", + "OPERACIONES: Dict[str, Callable] = {\n", + " \"sum\": lambda a, b: a + b,\n", + " \"sub\": lambda a, b: a - b,\n", + " \"mul\": lambda a, b: a * b,\n", + " \"div\": safe_div,\n", + " \"pow\": potencia, # exp entero\n", + " \"fact\": factorial, # unario\n", + " \"gcd\": mcd, # entero\n", + "}\n", + "\n", + "def calcular(op: str, *args) -> float:\n", + " \"\"\"\n", + " Enrutador de operaciones con manejo de errores y else/finally.\n", + " Uso:\n", + " calcular('sum', 2, 3) -> 5\n", + " calcular('div', 10, 2) -> 5\n", + " calcular('pow', 2, 8) -> 256\n", + " calcular('fact', 5) -> 120\n", + " calcular('gcd', 48, 18) -> 6\n", + " \"\"\"\n", + " try:\n", + " if op not in OPERACIONES:\n", + " raise KeyError(f\"Operación desconocida '{op}'. Disponibles: {', '.join(OPERACIONES)}\")\n", + " fn = OPERACIONES[op]\n", + "\n", + " # Validaciones según aridad conocida\n", + " if fn is factorial:\n", + " if len(args) != 1 or not isinstance(args[0], int):\n", + " raise InputTypeError(\"factorial espera 1 entero.\")\n", + " elif fn is mcd:\n", + " if len(args) != 2 or not all(isinstance(x, int) for x in args):\n", + " raise InputTypeError(\"gcd espera 2 enteros.\")\n", + " elif fn is potencia:\n", + " if len(args) != 2 or not isinstance(args[1], int):\n", + " raise InputTypeError(\"pow espera (base: float|int, exp: int).\")\n", + " else:\n", + " if len(args) != 2:\n", + " raise InputTypeError(f\"'{op}' espera 2 argumentos numéricos.\")\n", + "\n", + " res = fn(*args)\n", + " except (InputTypeError, MathDomainError, KeyError, TypeError, ValueError) as e:\n", + " print(f\"Error en calcular: {e}\")\n", + " raise\n", + " else:\n", + " return float(res) if isinstance(res, (int, float)) else res\n", + " finally:\n", + " # lugar para logs/telemetría/cierre de recursos si hiciera falta\n", + " pass\n", + "\n", + "# ==========================\n", + "# Mini tests / demostración\n", + "# ==========================\n", + "if __name__ == \"__main__\":\n", + " # 1) get_unique_list\n", + " print(get_unique_list([1, 2, 2, 3, 1, 4])) # [1, 2, 3, 4]\n", + "\n", + " # 2) count_case\n", + " print(count_case(\"Hola MUndo!!\")) # {'upper': 3, 'lower': 7}\n", + "\n", + " # 3) remove_punctuation\n", + " print(remove_punctuation(\"Hola, mundo! ¿Todo bien?\")) # \"Hola mundo ¿Todo bien\" (sin ! ,)\n", + "\n", + " # 4) word_count\n", + " print(word_count(\"Hola hola, mundo Mundo!\")) # {'hola': 2, 'mundo': 2}\n", + "\n", + " # 5) Calculadora\n", + " print(calcular(\"sum\", 2, 5)) # 7.0\n", + " print(calcular(\"div\", 10, 2)) # 5.0\n", + " print(calcular(\"pow\", 2, 8)) # 256.0\n", + " print(calcular(\"fact\", 5)) # 120.0\n", + " print(calcular(\"gcd\", 48, 18)) # 6.0\n" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "venv", "language": "python", "name": "python3" }, @@ -66,7 +316,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.12.2" } }, "nbformat": 4,