diff --git a/sum23/lecture_materials/15_Errors/lec-15-worksheet.pdf b/sum23/lecture_materials/15_Errors/lec-15-worksheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0a57c1e39f3c8a5e4c3074944ebe86dec09b02b8 Binary files /dev/null and b/sum23/lecture_materials/15_Errors/lec-15-worksheet.pdf differ diff --git a/sum23/lecture_materials/15_Errors/lec_15_error_handling_template.ipynb b/sum23/lecture_materials/15_Errors/lec_15_error_handling_template.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..17918b5a678e912a295fd01e76d40617b2fc963f --- /dev/null +++ b/sum23/lecture_materials/15_Errors/lec_15_error_handling_template.ipynb @@ -0,0 +1,805 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Error handling" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# import statements\n", + "import math" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Warmup\n", + "\n", + "### Warmup 1: How does this recursion work? Try it in Python tutor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def chop(s):\n", + " if len(s) < 3:\n", + " return s[0]\n", + " else:\n", + " return s[1] + chop(s[2:])\n", + "\n", + "chop(\"abcdefghijklmnop\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Now, by hand, figure out this output\n", + "chop(\"987654\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Lecture 25: Error Handling\n", + "\n", + "**Learing Objectives:**\n", + " \n", + "- Explain the purpose of assert statements, try/except blocks, and raise statements.\n", + "\n", + "- Use an assert statement to force a program to crash, and trace code that uses assert.\n", + "\n", + "- Use try/except blocks to catch runtime errors and deal with them\n", + " - by specifying the exception(s) caught\n", + " - by using the exception object\n", + "\n", + "- Use the raise statement to raise an exception that may be caught in another part of the program\n", + "\n", + "- Hand trace code that uses assert, try/except blocks and raise statements\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why might you want your code to crash more?\n", + "\n", + "- It is easier to debug the program if we get a stack trace\n", + "- Semantic errors are the scariest because we don't get any kind of error\n", + "\n", + "### When is it fine for your code to crash less?\n", + "\n", + "- When user enters incorrect input, we simply want to display an error message and not crash\n", + "- When the program has syntax error, we definitely want the program to crash\n", + "\n", + "<div>\n", + "<img src=\"attachment:Theme.png\" width=\"600\"/>\n", + "</div>" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pizza Analyzer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def pizza_size(radius):\n", + " return (radius ** 2) * math.pi\n", + "\n", + "def slice_size(radius, slice_count):\n", + " total_size = pizza_size(radius)\n", + " return total_size * (1 / slice_count)\n", + "\n", + "def main():\n", + " for i in range(3):\n", + " # grab input\n", + " args = input(\"Enter pizza diameter(inches), slice count: \")\n", + " args = args.split(',')\n", + " radius = float(args[0].strip()) / 2\n", + " slices = int(args[1].strip())\n", + "\n", + " # pizza analysis\n", + " size = slice_size(radius, slices)\n", + " print('PIZZA: radius = {}, slices = {}, slice square inches = {}'\n", + " .format(radius, slices, size))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Try valid input of 4, 4 for main invocation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Can you think of any inputs that will make this program crash?\n", + "\n", + "Try each of these bad inputs:\n", + "- 10, 0: ZeroDivisionError\n", + "- 10: IndexError\n", + "- 10, hello: ValueError\n", + "- 10, 4.5: ValueError\n", + "- 10, -4: Semantic error\n", + "- -10, 4: Semantic error (scariest error for this example)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### What was the scariest error in the above examples?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# - -10, 4: Semantic error (scariest error for this example)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## `assert` statements\n", + "- `assert` statements enable you to convert semantic errors into runtime errors\n", + " - runtime errors are easier to debug than semantic errors\n", + "- `assert` statements make your program very slow!\n", + " - so sometimes programmers disable these (we won't be learning about this)\n", + "- Syntax: `assert BOOLEAN_EXPRESSION`\n", + " - BOOLEAN_EXPRESSION evaluates to `True`: nothing happens (move on to next line of code)\n", + " - BOOLEAN_EXPRESSION evaluates to `False`: program carshes with `AssertionError`\n", + " \n", + "<div>\n", + "<img src=\"attachment:Assert.png\" width=\"450\"/>\n", + "</div>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# simple example\n", + "\n", + "age = int(input(\"Enter your age: \"))\n", + "assert age >= 0 # if True, do nothing else, crash\n", + "print(\"In five years you will be\", age + 5, \"years old\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<div>\n", + "<img src=\"attachment:assert.png\" width=\"600\"/>\n", + "</div>" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's improvise error handling in pizza analyzer\n", + "- using `assert`:\n", + " - `assert` that radius is positive\n", + " - `assert` that slice count is positive" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Copy pasted code (not a typical thing which I will ask you to do) \n", + "# change function definitions to v2 and add the assert statements\n", + "\n", + "def pizza_size_v2(radius):\n", + " # TODO add assert statement here\n", + " return (radius ** 2) * math.pi\n", + "\n", + "def slice_size_v2(radius, slice_count):\n", + " # TODO add assert statement here\n", + " total_size = pizza_size(radius)\n", + " return total_size * (1 / slice_count)\n", + "\n", + "def main_v2():\n", + " for i in range(3):\n", + " # grab input\n", + " args = input(\"Enter pizza diameter(inches), slice count: \")\n", + " args = args.split(',')\n", + " radius = float(args[0].strip()) / 2\n", + " slices = int(args[1].strip())\n", + "\n", + " # pizza analysis\n", + " size = slice_size(radius, slices)\n", + " print('PIZZA: radius = {}, slices = {}, slice square inches = {}'\n", + " .format(radius, slices, size))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Try these problematic inputs for the main() function invocation\n", + "\n", + "- 10, -4: Semantic error\n", + "- -10, 4: Semantic error (scariest error for this example)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main_v2()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What if we want to keep running even if there is an error?\n", + "\n", + "That is, we don't want to pause the program execution for user's incorrect input" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## try / except blocks\n", + "\n", + "- `try` and `except` blocks come in pairs (runtime errors are “exceptions”)\n", + "- Python tries to run the code in the `try` block. \n", + " - If there is an exception, `try` block execution terminates and then `except` block gets executed(instead of crashing). This is called “catching” the exception.\n", + " - If there is no exception, `except` block doesn't get executed.\n", + "- Syntax (example):\n", + "```python\n", + "try:\n", + " flaky_function()\n", + "except:\n", + " print(\"error!\") # or some other handling\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### try / except examples\n", + "Try these examples using PythonTutor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 1: v1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " print(\"2 inverse is\", 1/2)\n", + " print(\"1 inverse is\", 1/1)\n", + " print(\"0 inverse is\", 1/0)\n", + " print(\"-1 inverse is\", -1/1)\n", + " print(\"-2 inverse is\", -1/1)\n", + "except:\n", + " print(\"that's all, folks!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 1: v2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " print(\"2 inverse is\", 1/2)\n", + " print(\"1 inverse is\", 1/1)\n", + " print(\"0 inverse is\", 1/0)\n", + "except:\n", + " print(\"that's all, folks!\")\n", + "\n", + "try:\n", + " print(\"-1 inverse is\", -1/1)\n", + " print(\"-2 inverse is\", -1/1)\n", + "except:\n", + " print(\"This will never get executed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 2: v1\n", + "- hierarchy of catching exceptions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def buggy():\n", + " print(\"buggy: about to fail\")\n", + " print(\"buggy: infinity is \", 1/0)\n", + " print(\"buggy: oops!\") # never prints\n", + "\n", + "def g():\n", + " print(\"g: before buggy\")\n", + " buggy()\n", + " print(\"g: after buggy\") # never prints\n", + "\n", + "def f():\n", + " try:\n", + " print(\"f: let's call g\")\n", + " g()\n", + " print(\"f: g returned normally\") # never prints\n", + " except:\n", + " print(\"f: that didn't go so well\")\n", + "\n", + "f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 2: v2\n", + "- hierarchy of catching exceptions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def buggy():\n", + " print(\"buggy: about to fail\")\n", + " print(\"buggy: infinity is \", 1/0)\n", + " print(\"buggy: oops!\") # never prints\n", + "\n", + "def g():\n", + " print(\"g: before buggy\")\n", + " try:\n", + " buggy()\n", + " except:\n", + " print(\"g: that didn't go well\")\n", + " print(\"g: after buggy\") \n", + "\n", + "def f():\n", + " try:\n", + " print(\"f: let's call g\")\n", + " g()\n", + " print(\"f: g returned normally\") \n", + " except:\n", + " print(\"f: that didn't go so well\") # never prints\n", + "\n", + "f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 2: v3\n", + "- hierarchy of catching exceptions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def buggy():\n", + " try:\n", + " print(\"buggy: about to fail\")\n", + " print(\"buggy: infinity is \", 1/0)\n", + " except:\n", + " print(\"buggy: oops!\") \n", + "\n", + "def g():\n", + " print(\"g: before buggy\")\n", + " try:\n", + " buggy()\n", + " except:\n", + " print(\"g: that didn't go well\") # never prints\n", + " print(\"g: after buggy\")\n", + "\n", + "def f():\n", + " try:\n", + " print(\"f: let's call g\")\n", + " g()\n", + " print(\"f: g returned normally\") \n", + " except:\n", + " print(\"f: that didn't go so well\") # never prints\n", + "\n", + "f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### What if we want to know the reason for the exception?\n", + "\n", + "- Syntax (example):\n", + "```python\n", + "try:\n", + " flaky_function()\n", + "except Exception as e:\n", + " print(\"error because:\", str(e))\n", + " print(\"type of exception:\", type(e))\n", + "```\n", + "- `Exception` is a type.\n", + "- e an object instance of of type `Exception` (very general)\n", + " - there are different types of exceptions\n", + "- `str(e)` gives you the reason for the exception.\n", + "- `type(e)` will give you the type of the exception." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let's improvise error handling in pizza analyzer more\n", + "- using `try` ... `except` ... " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def main_v3():\n", + " for i in range(3):\n", + " # grab input\n", + " args = input(\"Enter pizza diameter(inches), slice count: \")\n", + " args = args.split(',')\n", + " radius = float(args[0].strip()) / 2\n", + " slices = int(args[1].strip())\n", + "\n", + " # pizza analysis TODO add try/except here\n", + " size = slice_size_v2(radius, slices)\n", + " print('PIZZA: radius = {}, slices = {}, slice square inches = {}'\n", + " .format(radius, slices, size))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Try these problematic inputs for the main() function invocation\n", + "\n", + "- 10, 0: ZeroDivisionError\n", + "- 10: IndexError\n", + "- 10, hello: ValueError\n", + "- 10, 4.5: ValueError\n", + "- 10, -4: AssertionError (after we wrote assert statement)\n", + "- -10, 4: AssertionError (after we wrote assert statement)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main_v3()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `Exception` is too broad\n", + "- it catches many types of exceptions\n", + "- you do not want your except block to catch every possible exception!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's create an intentional SyntaxError\n", + "\n", + "def main_v4():\n", + " for i in range(5):\n", + " try:\n", + " # grab input\n", + " args = input(\"Enter pizza diameter(inches), slice count: \")\n", + " args = args.split(',')\n", + " radius = float(args[0].strip()) / 2\n", + " slices = int(args[1].strip())\n", + " except Exception as e:\n", + " print(\"Bad input & reason is:\", str(e))\n", + " print(\"Type of exception:\", type(e))\n", + " \n", + " try:\n", + " # pizza analysis\n", + " size = slice_size_v2(radius, slices)\n", + " print('PIZZA: radius = {}, slices = {}, slice square inches = {}'\n", + " .format(radius, slices, size))\n", + " except Exception as e:\n", + " print(\"Pizza analysis error & reason is:\", str(e))\n", + " print(\"Type of exception:\", type(e))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main_v4() # oops, we made our program way too robus!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How can we make `except` block catch specific exceptions?\n", + "\n", + "- Syntax (example):\n", + "```python\n", + "try:\n", + " flaky_function()\n", + "except (ValueError, IndexError) as e:\n", + " print(\"error because:\", str(e))\n", + " print(\"type of exception:\", type(e))\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's fix our except blocks\n", + "# Try introducing the intentional SyntaxError now\n", + "\n", + "def main_v5():\n", + " for i in range(5):\n", + " try:\n", + " # grab input\n", + " args = input(\"Enter pizza diameter(inches), slice count: \")\n", + " args = args.split(',')\n", + " radius = float(args[0].strip()) / 2\n", + " slices = int(args[1].strip())\n", + " except Exception as e:\n", + " print(\"Bad input & reason is:\", str(e))\n", + " print(\"Type of exception:\", type(e))\n", + " continue\n", + " \n", + " try:\n", + " # pizza analysis\n", + " size = slice_size_v2(radius, slices)\n", + " print('PIZZA: radius = {}, slices = {}, slice square inches = {}'\n", + " .format(radius, slices, size))\n", + " except Exception as e:\n", + " print(\"Pizza analysis error & reason is:\", str(e))\n", + " print(\"Type of exception:\", type(e))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Try these problematic inputs for the main() function invocation\n", + "\n", + "- 10, 0: ZeroDivisionError\n", + "- 10: IndexError\n", + "- 10, hello: ValueError\n", + "- 10, 4.5: ValueError\n", + "- 10, -4: AssertionError (after we wrote assert statement)\n", + "- -10, 4: AssertionError (after we wrote assert statement)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main_v5()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exception hierarchy\n", + "- It helps to know some common excpeptions and to know their hierarchy.\n", + "- Don't try to memorize this but do make a note of the exceptions that occur the most often.\n", + "- Python documentation: https://docs.python.org/3/library/exceptions.html." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### `raise` statements\n", + "- Rather than using assert we can also raise a specific error. \n", + "\n", + "- Syntax (example):\n", + "```python\n", + "if BOOLEAN_CONDITION:\n", + " raise ArithmeticError(\"details of error\")\n", + "```\n", + "\n", + "<div>\n", + "<img src=\"attachment:raise.png\" width=\"500\"/>\n", + "</div>" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# simple example: raise exceptions instead of using assert statements\n", + "\n", + "age = int(input(\"enter your age: \"))\n", + "if age < 0:\n", + " raise ArithmeticError(\"age can't be negative\") # we can 'raise' a relevant exception\n", + "print(\"in five years you will be\", age + 5, \"years old\") " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def pizza_size_v2(radius):\n", + " assert radius >= 0\n", + " return (radius ** 2) * math.pi\n", + "\n", + "def slice_size_v2(radius, slice_count):\n", + " assert slice_count >= 0\n", + " total_size = pizza_size_v2(radius)\n", + " return total_size * (1 / slice_count)\n", + "\n", + "def main_v6():\n", + " for i in range(5):\n", + " # grab input\n", + " try:\n", + " args = input(\"Enter pizza diameter(inches), slice count: \")\n", + " args = args.split(',')\n", + " radius = float(args[0].strip()) / 2\n", + " slices = int(args[1].strip())\n", + " except (ValueError, TypeError, IndexError) as e:\n", + " print(\"Bad input & reason is:\", str(e))\n", + " print(\"Type of exception:\", type(e))\n", + " continue\n", + "\n", + " # pizza analysis\n", + " try:\n", + " size = slice_size_v2(radius, slices)\n", + " print('PIZZA: radius={}, slices={}, slice square inches={}'\n", + " .format(radius, slices, size))\n", + " except (ZeroDivisionError, AssertionError) as e:\n", + " print(\"Pizza analysis error!\", str(e))\n", + " print(\"Type of exception:\", type(e))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Try these problematic inputs for the main() function invocation\n", + "\n", + "- 10, -4: ArithmeticError (after we wrote assert statement)\n", + "- -10, 4: ArithmeticError (after we wrote assert statement)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main_v6()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}