From 732046428a32cb9dd02dd47514ba9f6326ef5ea3 Mon Sep 17 00:00:00 2001 From: James Turk Date: Sun, 27 Oct 2024 21:58:48 -0500 Subject: [PATCH] exceptions --- 11.exceptions.ipynb | 498 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 11.exceptions.ipynb diff --git a/11.exceptions.ipynb b/11.exceptions.ipynb new file mode 100644 index 0000000..b96deed --- /dev/null +++ b/11.exceptions.ipynb @@ -0,0 +1,498 @@ +{ + "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": 1, + "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": 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": 3, + "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[3], 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[1], 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": 14, + "id": "5022b7c8-a19f-45cb-9d40-58abc48de961", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "can't divide by zero, set c = 0\n" + ] + } + ], + "source": [ + "try:\n", + " a = 3\n", + " b = 0\n", + " c = a / b\n", + "except ZeroDivisionError:\n", + " c = 0\n", + " print(f\"can't divide by zero, set c = {c}\")" + ] + }, + { + "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", + " something()\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 Exception: # catches all runtime exceptions\n", + " ...\n", + "except ArithmeticError: # catches all errors derived from ArithmeticError\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": 12, + "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": 10, + "id": "58c691a9-14ed-4fe1-9b0d-eb9b52a4d44d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "9" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "5fd8e7ee-53c6-4cd1-8748-cd66f06da9ac", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "f requires a positive argument", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[11], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[9], line 3\u001b[0m, in \u001b[0;36mf\u001b[0;34m(positive)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mf\u001b[39m(positive):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m positive \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[0;32m----> 3\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mf requires a positive argument\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m positive \u001b[38;5;241m*\u001b[39m positive\n", + "\u001b[0;31mValueError\u001b[0m: f requires a positive argument" + ] + } + ], + "source": [ + "f(-1)" + ] + }, + { + "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", + "\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": 18, + "id": "320d3309-099a-4f2e-9096-983101bfc157", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "got a value error error message\n", + "always prints at the end\n" + ] + } + ], + "source": [ + "try:\n", + " #pass\n", + " raise ValueError(\"error message\")\n", + "except ValueError as e:\n", + " print(\"got a value error\", e)\n", + "else:\n", + " print(\"no exception raised\")\n", + "finally:\n", + " print(\"always prints at the end\")" + ] + }, + { + "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 +}