{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c22bb274-b34a-415d-96a5-aabee2a87b59",
   "metadata": {},
   "source": [
    "# Exceptions in Python\n",
    "\n",
    "Most of the time we're concerned with the \"correct\" path through a program.\n",
    "Sometimes things can go wrong, and we need to take an \"exceptional\" path."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c69f70a0-f070-4d3c-adaa-a4520cd73af5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def my_func(a, b):\n",
    "    \"\"\" what can go wrong with this function? \"\"\"\n",
    "    if a > b:\n",
    "        return a / b\n",
    "    else:\n",
    "        return a * c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "6eabe23e-d4dc-442c-b405-4adc9fae7417",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.3333333333333333"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func(4, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "ade6c979-7fd0-460e-b6cf-be8cd800e230",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5.0"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_func(10, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1ef88eb5-0852-4d97-bd25-340b83730442",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "ZeroDivisionError",
     "evalue": "division by zero",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mZeroDivisionError\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mmy_func\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m10\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "Cell \u001b[0;32mIn[2], line 4\u001b[0m, in \u001b[0;36mmy_func\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m      2\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\" what can go wrong with this function? \"\"\"\u001b[39;00m\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m a \u001b[38;5;241m>\u001b[39m b:\n\u001b[0;32m----> 4\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mb\u001b[49m\n\u001b[1;32m      5\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m      6\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m a \u001b[38;5;241m*\u001b[39m c\n",
      "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero"
     ]
    }
   ],
   "source": [
    "my_func(10, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "776c8d17-0c17-49a6-9a1d-ef5fbaf7859b",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'c' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mmy_func\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m10\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "Cell \u001b[0;32mIn[1], line 6\u001b[0m, in \u001b[0;36mmy_func\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m      4\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m a \u001b[38;5;241m/\u001b[39m b\n\u001b[1;32m      5\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m----> 6\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m a \u001b[38;5;241m*\u001b[39m \u001b[43mc\u001b[49m\n",
      "\u001b[0;31mNameError\u001b[0m: name 'c' is not defined"
     ]
    }
   ],
   "source": [
    "my_func(2, 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7a97b68-43a1-4c10-8621-284d434b12a9",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "1191e7d1-4037-47d7-a899-4f0c0d6846ff",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'>' not supported between instances of 'int' and 'str'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mmy_func\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m10\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m1\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "Cell \u001b[0;32mIn[1], line 2\u001b[0m, in \u001b[0;36mmy_func\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmy_func\u001b[39m(a, b):\n\u001b[0;32m----> 2\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m>\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mb\u001b[49m:\n\u001b[1;32m      3\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m a \u001b[38;5;241m/\u001b[39m b\n\u001b[1;32m      4\u001b[0m     \u001b[38;5;28;01melse\u001b[39;00m:\n",
      "\u001b[0;31mTypeError\u001b[0m: '>' not supported between instances of 'int' and 'str'"
     ]
    }
   ],
   "source": [
    "my_func(10, \"1\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9b301d27-ce89-424f-bf21-cd42f7ef724e",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "unsupported operand type(s) for /: 'str' and 'str'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[8], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mmy_func\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mb\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43ma\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "Cell \u001b[0;32mIn[1], line 3\u001b[0m, in \u001b[0;36mmy_func\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmy_func\u001b[39m(a, b):\n\u001b[1;32m      2\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m a \u001b[38;5;241m>\u001b[39m b:\n\u001b[0;32m----> 3\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mb\u001b[49m\n\u001b[1;32m      4\u001b[0m     \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m      5\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m a \u001b[38;5;241m*\u001b[39m c\n",
      "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for /: 'str' and 'str'"
     ]
    }
   ],
   "source": [
    "my_func(\"b\", \"a\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c8c321f-0a0f-4116-94e0-cb91421c1492",
   "metadata": {},
   "source": [
    "We could try to think of all possible conditions, and our simple function would become much longer & harder to read. \n",
    "\n",
    "Often there is no better solution than to have an error occur, so Python **raises an exception** and that is what happens here."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0877c7a7-a636-47a1-a5d2-05ff8e5bb920",
   "metadata": {},
   "source": [
    "The above types are called rumtime exceptions, because we don't see them until the code runs.\n",
    "\n",
    "This is different from a `SyntaxError` which means that Python can't parse our statements:\n",
    "\n",
    "```\n",
    "# Syntax Errors\n",
    "def my_func(a, b,: # missing )\n",
    "    if a > b\n",
    "      return a / b  # missing colon\n",
    "    else { return a * c }      # python doesn't use braces\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14c7426d-9b73-4712-b204-db9cd6bcde67",
   "metadata": {},
   "source": [
    "## Built-in Exceptions\n",
    "\n",
    "* `Exception` (base type)\n",
    "  * `ValueError`\n",
    "  * `TypeError`\n",
    "  * `KeyError`\n",
    "  * `IndexError`\n",
    "  * `NotImplementedError`\n",
    "  * `OSError`\n",
    "      * `FileNotFoundError`\n",
    "\n",
    "And many more: https://docs.python.org/3/library/exceptions.html#exception-hierarchy\n",
    "\n",
    "Note that exceptions form a hierarchy, we'll discuss this in more detail when we come to inheritance.\n",
    "\n",
    "## Handling Exceptions\n",
    "\n",
    "In practice, exceptions are **raised**, and either **caught** or not.  An uncaught exception stops the program and prints an error message to the screen as you've seen.\n",
    "\n",
    "We handle exceptions with `try-except` blocks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5022b7c8-a19f-45cb-9d40-58abc48de961",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'd' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[5], line 5\u001b[0m\n\u001b[1;32m      3\u001b[0m     b \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[1;32m      4\u001b[0m     \u001b[38;5;66;03m#c = a / b\u001b[39;00m\n\u001b[0;32m----> 5\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\u001b[43md\u001b[49m)\n\u001b[1;32m      6\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mlast line of try\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m      7\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mZeroDivisionError\u001b[39;00m:\n",
      "\u001b[0;31mNameError\u001b[0m: name 'd' is not defined"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    a = 3\n",
    "    b = 0\n",
    "    #c = a / b\n",
    "    print(d)\n",
    "    print(\"last line of try\")\n",
    "except ZeroDivisionError:\n",
    "    c = 0\n",
    "    print(f\"can't divide by zero, set c = {c}\")\n",
    "#except NameError:\n",
    "#    print(\"used unknown name\")\n",
    "print(\"and then this happens\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c996bd03-f79b-49de-874d-cd6cb076a70a",
   "metadata": {},
   "source": [
    "If an exception occurs within a `try` block, execution will jump to the appropriate `except` block if one exists.\n",
    "\n",
    "Typically you want to handle different errors differently, you can also handle multiple errors with one block by using any of the following:\n",
    "\n",
    "```python\n",
    "# multiple except blocks for different behaviors\n",
    "try:\n",
    "    something()\n",
    "except ValueError:\n",
    "    ...\n",
    "except IndexError:\n",
    "    ...\n",
    "```\n",
    "\n",
    "```python\n",
    "# one block w/ multiple types in a tuple\n",
    "try:\n",
    "    somthing()\n",
    "except (ValueError, IndexError, KeyError):  # tuple of return values\n",
    "    ...\n",
    "```\n",
    "\n",
    "```python\n",
    "# base exception type, use sparingly\n",
    "try:\n",
    "    something()\n",
    "except ArithmeticError:   # catches all errors derived from ArithmeticError\n",
    "    ...\n",
    "except Exception:   # catches all runtime exceptions\n",
    "    ...\n",
    "```\n",
    "\n",
    "\n",
    "```python\n",
    "# DO NOT USE: bare except\n",
    "try:\n",
    "    something();\n",
    "except: \n",
    "    ...\n",
    "```\n",
    "\n",
    "```python\n",
    "try:\n",
    "    something()\n",
    "except OSError:   # will catch all subclasses of OSError\n",
    "    ...\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34060dea-77e4-420d-9895-5d866635f620",
   "metadata": {},
   "source": [
    "## Raising Exceptions\n",
    "\n",
    "To raise an exception, you use the `raise` keyword, which similarly to `return` exits a function immediately.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "fd6d0e12-eae2-466e-8536-db5d246091f0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(positive):\n",
    "    if positive < 0:\n",
    "        raise ValueError(\"f requires a positive argument\")\n",
    "    return positive * positive"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "58c691a9-14ed-4fe1-9b0d-eb9b52a4d44d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "5fd8e7ee-53c6-4cd1-8748-cd66f06da9ac",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "got an error:  f requires a positive argument\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    y = f(-1)\n",
    "except ValueError as exc:\n",
    "    y = 0\n",
    "    print(\"got an error: \", exc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "769fb253-ae47-4e47-a6fd-9a37e1ef3d5e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n"
     ]
    }
   ],
   "source": [
    "print(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f86298c9-b7b4-4f9c-81c5-ad16df15b233",
   "metadata": {},
   "source": [
    "### Defining Custom Exception Types\n",
    "\n",
    "Sometimes you can use an existing type, but in practice it is very common to want to define custom exception types.\n",
    "\n",
    "Custom exception types let you handle your programs errors differently from Python's built in types:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "af5d3624-3f4f-4f1d-9292-388f909a3bfd",
   "metadata": {},
   "outputs": [],
   "source": [
    "class InvalidColor(Exception):\n",
    "    \"\"\" This exception is raised when an invalid color is passed. \"\"\"\n",
    "    pass\n",
    "\n",
    "VALID_COLORS = (...)\n",
    "\n",
    "def draw_point(x, y, color):\n",
    "    if color not in VALID_COLORS:\n",
    "        raise InvalidColor(\"color should be one of the valid colors\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b82f7b0-49bd-4781-a2af-3d55a7ec0ae6",
   "metadata": {},
   "source": [
    "Exception classes must inherit from `Exception` or another exception.\n",
    "\n",
    "We'll talk more about inheritance next, but for now, just know that you need the `(Exception)` portion of the class declaration line."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d93053ab-4cd7-4751-bc81-3f6d13a7d908",
   "metadata": {},
   "source": [
    "## try-except-finally-else\n",
    "\n",
    "The full syntax for `try-except` includes two more optional clauses `finally` and `else`.\n",
    "\n",
    "These work somewhat like `elif`/`else`:\n",
    "\n",
    "```python\n",
    "try:\n",
    "    something()\n",
    "except Exception as e:   # \"as e\" allows using the exception if needed\n",
    "    ...   # executes only if exception was raised\n",
    "else:\n",
    "    ...   # executes if no exception raised\n",
    "finally:\n",
    "    ...   # executes after try/except/else no matter what\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "320d3309-099a-4f2e-9096-983101bfc157",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "else executed\n",
      "always prints at the end\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "   1 / 2\n",
    "except ValueError as e:\n",
    "    print(\"got a value error:\", e)\n",
    "except Exception as e:\n",
    "    print(\"got some other error:\", type(e), e)\n",
    "else:\n",
    "    print(\"else executed\")\n",
    "finally:\n",
    "    print(\"always prints at the end\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ed69652-c95d-4745-8aa2-ac2171d06ad5",
   "metadata": {},
   "source": [
    "## Best Practices\n",
    "\n",
    "Catch the narrowest exception that you need to, so that you don't accidentally \"handle\" exceptions that you don't intend to.\n",
    "\n",
    "Throw the most specific exception that you can, so that it can be handled by unique code if needed.\n",
    "\n",
    "Provide useful error messages as the argument to your exception. All exception types by default take a string that'll be used as a message:\n",
    "\n",
    "`raise ValueError(\"say why the value was rejected\")`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2d6c25cf-cd55-4d33-99f3-7b0d5f7eab86",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
