51042-notes/11.exceptions.ipynb
2024-10-30 21:39:04 -05:00

523 lines
18 KiB
Plaintext

{
"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": 10,
"id": "5022b7c8-a19f-45cb-9d40-58abc48de961",
"metadata": {
"tags": []
},
"outputs": [
{
"ename": "SyntaxError",
"evalue": "expected 'except' or 'finally' block (572013031.py, line 6)",
"output_type": "error",
"traceback": [
"\u001b[0;36m Cell \u001b[0;32mIn[10], line 6\u001b[0;36m\u001b[0m\n\u001b[0;31m print(\"hello\")\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m expected 'except' or 'finally' block\n"
]
}
],
"source": [
"try:\n",
" a = 3\n",
" b = 0\n",
" c = a / b\n",
" print(\"last line of try\")\n",
"except ZeroDivisionError:\n",
" c = 0\n",
" print(f\"can't divide by zero, set c = {c}\")\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",
" 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
}