{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'>&copy; 2015 Karim Pichara - Christian Pieringer. Todos los derechos reservados.</font>\n",
    "<br>\n",
    "<font size='1'>Modificado en 2017-1 al 2026-1 por Equipo Docente IIC2233.</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Levantando excepciones: `raise`](#Levantando-excepciones:-raise)\n",
    "2. [Manejo de Excepciones: `try` y `except`](#Manejo-de-Excepciones:-try-y-except)\n",
    "    1. [Múltiples excepciones con `except`](#múltiples-excepciones-con-except)\n",
    "    2. [Flujos complementarios: `else` y `finally`](#flujos-complementarios-else-y-finally)\n",
    "    3. [Observaciones](#observaciones)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Levantando excepciones: `raise`\n",
    "\n",
    "Hemos visto algunos tipos de excepciones existentes y las condiciones bajo las cuales se gatillan.\n",
    "\n",
    "Sin embargo, también podemos generar una excepción en el momento que queramos creando una nueva instancia de la excepción, y utilizando la sentencia **`raise`**. Cada excepción tiene un tipo definido, y es posible definir un mensaje explicativo para el usuario.\n",
    "\n",
    "Por ejemplo, la siguiente función `convert_coordinate` recibe un *string* que sigue el formato `\"coord_x,coord_y\"` y tiene por objetivo retornar una tupla con los valores enteros de la coordenada. Se podría lanzar excepciones de esta manera:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def convert_coordinate(coordinate_string):\n",
    "    # Si el input no es del tipo esperado\n",
    "    if not isinstance(coordinate_string, str):\n",
    "        # aquí se genera una excepción y se incluye información para el usuario.\n",
    "        raise TypeError(\"Coordinate must be a string\")\n",
    "\n",
    "    # Si el input no sigue el formato esperado\n",
    "    if \",\" not in coordinate_string:\n",
    "        # aquí se genera otra excepción y se incluye información para el usuario.\n",
    "        raise ValueError(\"Coordinate must be separated by a comma\")\n",
    "\n",
    "    coord_x, coord_y = coordinate_string.split(\",\")\n",
    "    return (int(coord_x), int(coord_y))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "56 1123\n"
     ]
    }
   ],
   "source": [
    "text_1 = \"56,1123\"\n",
    "x, y = convert_coordinate(text_1)\n",
    "print(x, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "Coordinate must be a string",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m not_text = [\u001b[32m43\u001b[39m, \u001b[32m3\u001b[39m]\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m x, y = \u001b[43mconvert_coordinate\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnot_text\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m      3\u001b[39m \u001b[38;5;28mprint\u001b[39m(x, y)\n",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 5\u001b[39m, in \u001b[36mconvert_coordinate\u001b[39m\u001b[34m(coordinate_string)\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mconvert_coordinate\u001b[39m(coordinate_string):\n\u001b[32m      2\u001b[39m     \u001b[38;5;66;03m# Si el input no es del tipo esperado\u001b[39;00m\n\u001b[32m      3\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(coordinate_string, \u001b[38;5;28mstr\u001b[39m):\n\u001b[32m      4\u001b[39m         \u001b[38;5;66;03m# aquí se genera una excepción y se incluye información para el usuario.\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mCoordinate must be a string\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m      7\u001b[39m     \u001b[38;5;66;03m# Si el input no sigue el formato esperado\u001b[39;00m\n\u001b[32m      8\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33m,\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m coordinate_string:\n\u001b[32m      9\u001b[39m         \u001b[38;5;66;03m# aquí se genera otra excepción y se incluye información para el usuario.\u001b[39;00m\n",
      "\u001b[31mTypeError\u001b[39m: Coordinate must be a string"
     ]
    }
   ],
   "source": [
    "not_text = [43, 3]\n",
    "x, y = convert_coordinate(not_text)\n",
    "print(x, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "Coordinate must be separated by a comma",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mValueError\u001b[39m                                Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m text_incorrect = \u001b[33m\"\u001b[39m\u001b[33m23 - 54\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m x, y = \u001b[43mconvert_coordinate\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtext_incorrect\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m      3\u001b[39m \u001b[38;5;28mprint\u001b[39m(x, y)\n",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 10\u001b[39m, in \u001b[36mconvert_coordinate\u001b[39m\u001b[34m(coordinate_string)\u001b[39m\n\u001b[32m      7\u001b[39m \u001b[38;5;66;03m# Si el input no sigue el formato esperado\u001b[39;00m\n\u001b[32m      8\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"\u001b[39m\u001b[33m,\u001b[39m\u001b[33m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m coordinate_string:\n\u001b[32m      9\u001b[39m     \u001b[38;5;66;03m# aquí se genera otra excepción y se incluye información para el usuario.\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m10\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mCoordinate must be separated by a comma\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m     12\u001b[39m coord_x, coord_y = coordinate_string.split(\u001b[33m\"\u001b[39m\u001b[33m,\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m     13\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m (\u001b[38;5;28mint\u001b[39m(coord_x), \u001b[38;5;28mint\u001b[39m(coord_y))\n",
      "\u001b[31mValueError\u001b[39m: Coordinate must be separated by a comma"
     ]
    }
   ],
   "source": [
    "text_incorrect = \"23 - 54\"\n",
    "x, y = convert_coordinate(text_incorrect)\n",
    "print(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las excepciones interrumpen todo el flujo del programa. Aún si la excepción ocurre dentro de una llamada a una función que ha sido llamada dentro de otras llamadas a función, todas las llamadas a funciones previas son interrumpidas y la excepción llega hasta el sistema operativo (a menos que sea capturada previamente como veremos más adelante). Notar que en los últimos dos ejemplos no se ejecutó el `print`, debido a que se interrumpió el programa antes.\n",
    "\n",
    "A continuación, puedes ver otro ejemplo, donde al instanciar un objeto podemos exigir que el inicializador tenga que recibir una tupla de dos elementos como parámetro de entrada, lanzando una excepción cuando recibimos un argumento de otro tipo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Circle:\n",
    "\n",
    "    def __init__(self, center):\n",
    "        if not isinstance(center, tuple):\n",
    "            raise TypeError(\"The 'center' parameter must be a tuple\")\n",
    "            # Recordar que cuando ocurre el raise, la ejecución se interrumpe.\n",
    "            print(\"This line is never printed\")\n",
    "        self.center = center\n",
    "\n",
    "    def __repr__(self):\n",
    "        return f\"El centro es {self.center}\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "El centro es (2, 3)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Caso correcto donde se crea un objeto usando una tupla.\n",
    "c1 = Circle((2,3))\n",
    "c1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "The 'center' parameter must be a tuple",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[8]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;66;03m# Caso que genera la excepción al usar una lista como entrada.\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m c2 = \u001b[43mCircle\u001b[49m\u001b[43m(\u001b[49m\u001b[43m[\u001b[49m\u001b[32;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[32;43m3\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m      3\u001b[39m c2\n",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[6]\u001b[39m\u001b[32m, line 5\u001b[39m, in \u001b[36mCircle.__init__\u001b[39m\u001b[34m(self, center)\u001b[39m\n\u001b[32m      3\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, center):\n\u001b[32m      4\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(center, \u001b[38;5;28mtuple\u001b[39m):\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mThe \u001b[39m\u001b[33m'\u001b[39m\u001b[33mcenter\u001b[39m\u001b[33m'\u001b[39m\u001b[33m parameter must be a tuple\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m      6\u001b[39m         \u001b[38;5;66;03m# Recordar que cuando ocurre el raise, la ejecución se interrumpe.\u001b[39;00m\n\u001b[32m      7\u001b[39m         \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mEsta línea nunca se imprime\u001b[39m\u001b[33m\"\u001b[39m)\n",
      "\u001b[31mTypeError\u001b[39m: The 'center' parameter must be a tuple"
     ]
    }
   ],
   "source": [
    "# Caso que genera la excepción al usar una lista como entrada.\n",
    "c2 = Circle([2,3])\n",
    "c2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Manejo de Excepciones: `try` y `except`\n",
    "\n",
    "Cada vez que se **levanta** una excepción durante la ejecución del código, es posible **atraparla** mediante el uso de las sentencias `try` y `except`.\n",
    "\n",
    "La sentencia `try` permite definir un *scope* (bloque de código). Si se levanta una excepción dentro del *scope* de `try`, entonces la excepción es **capturada**. A continuación del bloque de `try` debe haber una o más instrucciones `except`. Las instrucciones `except` permiten implementar el manejo de la excepción capturada.\n",
    "\n",
    "En el momento que se captura una excepción dentro de `try` el flujo del programa salta inmediatamente al bloque de una de las sentencias `except`. Una vez que el bloque `except` ha terminado, el programa continúa en la instrucción **posterior** al bloque `try`/`except`. El programa **NO regresa** a la sentencia que gatilló la excepción.\n",
    "\n",
    "Como se mencionó al inicio de esta sección, solo se atrapan excepciones que surgen durante la ejecución del código. Esto implica que excepciones del tipo `SyntaxError` o `IndentationError` no son posibles de **atrapar** porque estas surgen durante la lectura del programa, no su ejecución.\n",
    "\n",
    "Volvamos al caso de la función `divide`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# No se atrapa ninguna excepción, por lo tanto no se previene un posible error.\n",
    "def divide(num, den):\n",
    "    # Esta función terminará el programa cuando el\n",
    "    # denominador den sea 0.\n",
    "    return float(num) / float(den)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.25\n"
     ]
    },
    {
     "ename": "ZeroDivisionError",
     "evalue": "float division by zero",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mZeroDivisionError\u001b[39m                         Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 4\u001b[39m\n\u001b[32m      2\u001b[39m w = divide(\u001b[32m5\u001b[39m, r)\n\u001b[32m      3\u001b[39m \u001b[38;5;28mprint\u001b[39m(w)\n\u001b[32m----> \u001b[39m\u001b[32m4\u001b[39m z = \u001b[43mdivide\u001b[49m\u001b[43m(\u001b[49m\u001b[32;43m5\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mr\u001b[49m\u001b[43m \u001b[49m\u001b[43m-\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m4\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m      5\u001b[39m \u001b[38;5;28mprint\u001b[39m(z)\n\u001b[32m      6\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33mYa hice ambas divisiones.\u001b[39m\u001b[33m\"\u001b[39m)\n",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[14]\u001b[39m\u001b[32m, line 5\u001b[39m, in \u001b[36mdivide\u001b[39m\u001b[34m(num, den)\u001b[39m\n\u001b[32m      2\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mdivide\u001b[39m(num, den):\n\u001b[32m      3\u001b[39m     \u001b[38;5;66;03m# Esta función terminará el programa cuando el\u001b[39;00m\n\u001b[32m      4\u001b[39m     \u001b[38;5;66;03m# denominador den sea 0.\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m5\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mfloat\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mnum\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[43m/\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mfloat\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mden\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[31mZeroDivisionError\u001b[39m: float division by zero"
     ]
    }
   ],
   "source": [
    "r = 4\n",
    "w = divide(5, r)\n",
    "print(w)\n",
    "z = divide(5, r - 4)\n",
    "print(z)\n",
    "print(\"Ya hice ambas divisiones.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Primero probaremos \"protegiendo\" un llamado correcto a `divide`, usando un bloque `try`/`except`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.25\n",
      "I already did the division.\n",
      "The program continues after the try/except\n"
     ]
    }
   ],
   "source": [
    "r = 4\n",
    "try:\n",
    "    # Dentro de este bloque ejecutamos el código que PODRÍA\n",
    "    # arrojar una excepción.\n",
    "    print(divide(5, r))\n",
    "    print(\"I already did the division.\")\n",
    "\n",
    "except (ZeroDivisionError) as error:\n",
    "    # Aquí manejamos la excepción que pueda ser lanzada en\n",
    "    # el bloque anterior. Si un error del tipo ZeroDivisonError\n",
    "    # ocurre, se ejecuta este bloque y el resto del programa\n",
    "    # continúa su ejecución normal. La excepción, como objeto,\n",
    "    # se puede acceder con la variable error.\n",
    "    print(f\"Error: {error}\")\n",
    "    print(\"How could you think of dividing by zero? Why are you like this?\")\n",
    "\n",
    "print(\"The program continues after the try/except\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En este caso no se generó la excepción, por lo tanto, no se ejecutó el código dentro de `except` y el programa siguió su flujo normal.\n",
    "\n",
    "Ahora probaremos protegiendo un caso en el que los argumentos de la división genera la excepción, ya que el denominador es 0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.25\n",
      "Error: float division by zero\n",
      "How could you think of dividing by zero? Why are you like this?\n",
      "The program continues after the try/except\n"
     ]
    }
   ],
   "source": [
    "# Manejo de la excepción con argumentos inválidos.\n",
    "# En este caso la función dará un error debido a que el denominador\n",
    "# utilizado es 0.\n",
    "r = 4\n",
    "try:\n",
    "    print(divide(5, r))\n",
    "    print(divide(5, r - 4))\n",
    "    print(\"I already did both divisions.\")\n",
    "\n",
    "except ZeroDivisionError as error:\n",
    "    print(f\"Error: {error}\")\n",
    "    print(\"How could you think of dividing by zero? Why are you like this?\")\n",
    "\n",
    "print(\"The program continues after the try/except\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podemos observar que cuando se produjo la excepción dentro del llamado `divide(5, r - 4)`, se generó una excepción de tipo `ZeroDivisionError`. Como esta excepción no fue capturada dentro de `divide`, entonces la excepción se propagó al llamado anterior, pero este llamado sí estaba dentro de un `try`. El flujo del programa salta hacia la sentencia `except` que está asociada al tipo de la excepción (`ZeroDivisionError`) y ejecuta lo que hay dentro de ella. Una vez que se ha ejecutado todo lo que había dentro de `except ZeroDivisionError`, el flujo del programa continúa normalmente.\n",
    "\n",
    "Pongamos atención a dos cosas: (1) la instrucción `print(\"I already did both divisions.\")` nunca se ejecuta porque al capturar la excepción, el flujo salta inmediatamente al `except`; (2) como la excepción fue exitosamente manejada, una vez que el `except` se completó, el programa siguió su flujo normal y, a diferencia de los ejemplos anteriores, no se \"cayó\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cómo se mencionó al inicio de esta sección, solo se atrapan excepciones que surgen durante la ejecución del código. Esto implica que excepciones del tipo `SyntaxError` o `IndentationError` no son posible de **atrapar** en el mismo archivo porque estas surgirán durante la lectura del programa, no su ejecución."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "unterminated string literal (detected at line 3) (2911640291.py, line 3)",
     "output_type": "error",
     "traceback": [
      "  \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[19]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[31m    \u001b[39m\u001b[31mprint(\"Anya\u001b[39m\n          ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m unterminated string literal (detected at line 3)\n"
     ]
    }
   ],
   "source": [
    "# Atrapar SyntaxError en el mismo código --> Fallará.\n",
    "try:\n",
    "    print(\"Anya\n",
    "except SyntaxError:\n",
    "    print(\"SyntaxError detected\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No obstante, igual existen formas de atrapar estos errores. Una de ellas es utilizar `import` de un posible archivo con errores de sintaxis o indentación, y dejar el `import archivo` dentro de un `try/excepy`. Esta solución logrará atrapar este tipo de error porque durante la ejecución del `import` es que recién se intentará leer el archivo con `SyntaxError`.\n",
    "\n",
    "En el siguiente ejemplo se dejó el mismo código con error `print(\"Anya` en un archivo llamado `syntax_error_file.py` y ahora se hará `import` de él, pero atrapando la excepción de `SyntaxError`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SyntaxError detected\n"
     ]
    }
   ],
   "source": [
    "# Atrapar SyntaxError desde un import --> Funcionará.\n",
    "try:\n",
    "    import syntax_error_file\n",
    "except SyntaxError:\n",
    "    print(\"SyntaxError detected\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Múltiples excepciones con `except`\n",
    "\n",
    "Con la sentencia `except` podemos incluir varios tipos de excepciones para manejar. En el siguiente caso, las causas que generarán las excepciones son distintas, pero el tratamiento para todas ellas será el mismo. Si lo que necesitamos es realizar un tratamiento diferenciado según el tipo de excepción, entonces podemos añadir más bloques de `except`, en donde cada uno tiene definido las excepciones sobre las que debe actuar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "def convert_coordinate(coordinate_string):\n",
    "    # Verificamos que el input es del tipo esperado.\n",
    "    if not isinstance(coordinate_string, str):\n",
    "        # Intencionalmente levantamos la excepción. Recuerda que las excepciones son objetos.\n",
    "        raise TypeError(\"Coordenada debe ser un string\")\n",
    "\n",
    "    # Verificamos que el input sigue el formato esperado.\n",
    "    if \",\" not in coordinate_string:\n",
    "        # El mensaje incluido en la excepción es el que se despliega\n",
    "        # cuando la manejamos después.\n",
    "        raise ValueError(\"Coordenada debe estar separada por una coma\")\n",
    "\n",
    "    coord_x, coord_y = coordinate_string.split(\",\")\n",
    "    return (int(coord_x), int(coord_y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora manejamos la excepción que pueda ser lanzada durante la ejecución de la función `convertir_coordenada`.\n",
    "\n",
    "El primer caso levantará una excepción debido a que el argumento no es válido al no ser del tipo esperado (no es de tipo `str`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Error: Coordenada debe ser un string\n",
      "Check the argument type.\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    print(convert_coordinate([42, 3]))\n",
    "\n",
    "# En esta parte manejamos las excepciones una vez que son lanzadas.\n",
    "except TypeError as error:\n",
    "    # Este bloque sólo maneja excepciones del tipo TypeError.\n",
    "    print(f\"Error: {error}\")\n",
    "    print(\"Check the argument type.\")\n",
    "\n",
    "except ValueError as error:\n",
    "    # Este bloque sólo maneja excepciones del tipo ValueError.\n",
    "    print(f\"Error: {error}\")\n",
    "    print(\"A ValueError occurred. Please verify your values.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En este segundo caso, se levantará una excepción porque el argumento no sigue el formato esperado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Error: Coordenada debe estar separada por una coma\n",
      "A ValueError occurred. Please verify your values.\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    print(convert_coordinate(\"32 - 190\"))\n",
    "\n",
    "# En esta parte manejamos las excepciones una vez que son lanzadas.\n",
    "except TypeError as error:\n",
    "    # Este bloque sólo maneja excepciones del tipo TypeError.\n",
    "    print(f\"Error: {error}\")\n",
    "    print(\"Check the argument type.\")\n",
    "\n",
    "except ValueError as error:\n",
    "    # Este bloque sólo maneja excepciones del tipo ValueError.\n",
    "    print(f\"Error: {error}\")\n",
    "    print(\"A ValueError occurred. Please verify your values.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También, es posible capturar varios tipos de errores con un solo bloque `except`. Se logra entregando una tupla de tipos de excepción en vez de solamente un tipo. Como para el siguiente ejemplo de una función divide personalizada, que agrupa los errores `ZeroDivisionError` y `TypeError`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def divide(num, den):\n",
    "    if not (isinstance(num, int) and isinstance(den, int)):\n",
    "        raise TypeError(\"Type error in numerator or denominator. :'(\")\n",
    "\n",
    "    if num < 0 or den < 0:\n",
    "        raise ValueError(\"There is a negative value in numerator or denominator >:(\")\n",
    "\n",
    "    return float(num) / float(den)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calling divide(45, '5')\n",
      "Error: Type error in numerator or denominator. :'(\n",
      "Check the division data. There is an incorrect type.\n",
      "\n",
      "Calling divide(-23, 5)\n",
      "Error: There is a negative value in numerator or denominator >:(\n",
      "A ValueError occurred. Please verify your values.\n",
      "\n",
      "Calling divide(4, 0)\n",
      "Error: float division by zero\n",
      "Check the division data. There is an incorrect type.\n",
      "\n",
      "Calling divide(21, 7)\n",
      "3.0\n"
     ]
    }
   ],
   "source": [
    "for arguments in [(45, \"5\"), (-23, 5), (4, 0), (21, 7)]:\n",
    "    try:\n",
    "        print(f\"Calling divide{arguments}\")\n",
    "        print(divide(*arguments))\n",
    "\n",
    "    # En esta parte manejamos las excepciones una vez que son lanzadas.\n",
    "    except (ZeroDivisionError, TypeError) as error:\n",
    "        # Este bloque maneja excepciones del tipo ZeroDivisionError o TypeError.\n",
    "        print(f\"Error: {error}\")\n",
    "        print(\"Check the division data. There is an incorrect type.\\n\")\n",
    "\n",
    "    except ValueError as error:\n",
    "        # Este bloque sólo maneja excepciones del tipo ValueError.\n",
    "        print(f\"Error: {error}\")\n",
    "        print(\"A ValueError occurred. Please verify your values.\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Flujos complementarios: `else` y `finally`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El bloque de `try` y `except` puede ser complementado opcionalmente con las sentencias **`else`** y **`finally`**:\n",
    "\n",
    "- Las instrucciones dentro del bloque `else` se ejecutarán **siempre y cuando no se haya lanzado ninguna excepción**.\n",
    "- En el bloque de la sentencia `finally` van instrucciones que se realizan **siempre, independientemente de si ocurrió una excepción o no**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Check the input data. They are either not ints or the denominator is 0!\n",
      "Always remember to use exceptions to handle errors in your program\n"
     ]
    }
   ],
   "source": [
    "# Esta corresponde a la estructura completa de try and except.\n",
    "try:\n",
    "    # Probamos si es posible realizar la operación.\n",
    "    result = divide(10, 0)\n",
    "    print(\"This line does not execute if an exception occurs in the previous line.\")\n",
    "\n",
    "except (ZeroDivisionError, TypeError):\n",
    "    # Este bloque opera para los tipos de excepciones definidos.\n",
    "    print(\"Check the input data. They are either not ints or the denominator is 0!\")\n",
    "\n",
    "except ValueError:\n",
    "    # Este bloque sólo maneja excepciones del tipo ValueError.\n",
    "    print(\"The input values are negative\")\n",
    "\n",
    "else:\n",
    "    # Como no hubo excepciones puede retornar normalmente el resultado\n",
    "    # En este caso, si se coloca un return después de la operación y\n",
    "    # esta es correcta, entonces nunca llegará a este punto.\n",
    "    print(\"OK! No errors with the data\")\n",
    "\n",
    "finally:\n",
    "    print(\"Always remember to use exceptions to handle errors in your program\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el ejemplo, si no se levanta ninguna excepción, también sería impreso el mensaje en la sección **`else`**. El siguiente ejemplo ilustra eso:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "This line does not execute if an exception occurs in the previous line.\n",
      "OK! No errors with the data\n",
      "Always remember to use exceptions to handle errors in your program\n"
     ]
    }
   ],
   "source": [
    "# Esta corresponde a la estructura completa de try and except.\n",
    "try:\n",
    "    # Probamos si es posible realizar la operación.\n",
    "    resultado = divide(10, 5)\n",
    "    print(\"This line does not execute if an exception occurs in the previous line.\")\n",
    "\n",
    "except (ZeroDivisionError, TypeError):\n",
    "    # Este bloque opera para los tipos de excepciones definidos.\n",
    "    print(\"Check the input data. They are either not ints or the denominator is 0!\")\n",
    "\n",
    "except ValueError:\n",
    "    # Este bloque sólo maneja excepciones del tipo ValueError.\n",
    "    print(\"The input values are negative\")\n",
    "\n",
    "else:\n",
    "    # Como no hubo excepciones puede retornar normalmente el resultado\n",
    "    # En este caso, si se coloca un return después de la operación y\n",
    "    # esta es correcta, entonces nunca llegará a este punto.\n",
    "    print(\"OK! No errors with the data\")\n",
    "\n",
    "finally:\n",
    "    print(\"Always remember to use exceptions to handle errors in your program\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El uso de **`finally`** es común en la ejecución de funciones de limpieza predefinidas, como por ejemplo, el cierre de un archivo después de ser procesado. Si ocurren errores mientras el archivo está abierto, éste quedará abierto. Es importante crear una rutina que asegure que, independientemente de si se lanza o no alguna excepción, el archivo sea cerrado correctamente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Error! Check the input data: they are either not ints or the denominator is 0!\n",
      "Always remember to close your files\n"
     ]
    }
   ],
   "source": [
    "from os import path\n",
    "\n",
    "\n",
    "fid = open(path.join(\"data\", \"log.txt\"), \"w\")\n",
    "\n",
    "try:\n",
    "    # Probamos si es posible realizar la apertura del archivo.\n",
    "    # En este caso se debe generar un error por que el denominador llega a ser 0.\n",
    "    for i in range(5, -1, -1):\n",
    "        fid.write(f\"{divide(10, i)}\")\n",
    "\n",
    "except (ZeroDivisionError, TypeError):\n",
    "    # Este bloque opera para los tipos de excepciones definidos.\n",
    "    print(\"Error! Check the input data: they are either not ints or the denominator is 0!\")\n",
    "\n",
    "else:\n",
    "    print(\"The file was created successfully!\")\n",
    "\n",
    "finally:\n",
    "    # Este bloque asegura que el archivo sea cerrado correctamente\n",
    "    # independientemente de si se produjo el error.\n",
    "    print(\"Always remember to close your files\")\n",
    "    fid.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una forma equivalente de hacer esto mismo es usar un *context manager* mediante la sentencia **`with`**. El siguiente ejemplo resume esta manera:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Error! Check the input data: they are either not ints or the denominator is 0!\n"
     ]
    }
   ],
   "source": [
    "from os import path\n",
    "\n",
    "\n",
    "with open(path.join(\"data\", \"log.txt\"), \"w\") as fid:\n",
    "    try:\n",
    "        # Probamos si es posible realizar la apertura del archivo.\n",
    "        # En este caso se debe generar un error por que el denominador llega a ser 0.\n",
    "        for i in range(5, -1, -1):\n",
    "            fid.write(f\"{divide(10, i)}\")\n",
    "\n",
    "    except (ZeroDivisionError, TypeError):\n",
    "        # Este bloque opera para los tipos de excepciones definidos.\n",
    "        print(\"Error! Check the input data: they are either not ints or the denominator is 0!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Observaciones\n",
    "\n",
    ">El manejo de excepciones es otra forma de control del flujo del programa, similar a lo que ocurre con la sentencia `if`. Entonces, ¿qué conviene más usar? ¿`if`/`else` o excepciones?. **Usar excepciones es más recomendable** que usar `if`/`elif`/`else` **para controlar errores**. Siempre es posible crear un sistema de códigos de error manejado por distintas salidas (`return`) de una función o módulo y suficientes combinaciones de `if`/`elif`/`else`. Sin embargo, esto puede generar casos particulares que complejizan, ensucian el diseño, y le quitan flexibilidad a nuestro programa. Además, continuamente hay que estar agregando nuevos códigos de error, lo que dificulta la mantenibilidad de nuestro código. \n",
    "\n",
    ">En general, las principales ventajas de usar excepciones por sobre `if`/`else` son:\n",
    ">- El programador está obligado a darles algún tratamiento, es decir, manejarlas o levantarlas. Mientras que los códigos de error pueden ser ignorados por el programador.\n",
    ">- El código queda más limpio y fácil de leer (recuerden que el código se lee muchas más veces de lo que se escribe).\n",
    ">- Todas las situaciones del programa son manejadas genéricamente, mientras que usando códigos de error tenemos la obligación de crear estructuras de control para cada función que implementemos.\n",
    ">- El manejo de excepciones permite \"notificar\" a otras aplicaciones sobre este tipo de situaciones, lo que no sería tan simple de lograr usando códigos de error inventados por el programador.\n",
    ">- ¿Por qué importa que el programa no falle inesperadamente?: Muchas veces exponer errores que no se han manejado a usuarios finales puede ser peligroso, ya que se podrían visualizar trozos de código en los outputs de estos. "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
