Chapter 7. Exceptions#

Basic Exception Handling#

When we execute a program, some statements may cause exceptions (basically unhandled errors). One of the most obvious examples is division by zero. Consider this example:

x = 2
y = 0
print(x / y)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[1], line 3
      1 x = 2
      2 y = 0
----> 3 print(x / y)

ZeroDivisionError: division by zero

If this happens during the execution of a script, the execution is stopped and the exception is shown to the user. But this doesn’t always seem ideal.

Consider writing calculator software. It would probably be bad, if the software just crashed every time we accidentally try to divide by zero. Instead we should show a meaningful error message to the user that explains what went wrong and ask him to try again. Let’s fix our example:

x = 2
y = 0

try:
    print(x / y)
except ZeroDivisionError:
    print("Uh-oh, you tried to divide by zero.")
    print("Luckily, we caught the exception and prevented you from destroying the universe!")
Uh-oh, you tried to divide by zero.
Luckily, we caught the exception and prevented you from destroying the universe!

Much better! We caught the error and our script may proceed, e.g. by asking the user to provide us with another number.

The try Statement and the except Clause#

The cornerstones of exception handling are the try statement and the except clause.

The general form of the try ... except ... construct looks as follows:

try:
    # Some statements
except MyException:
    # Some other statements

Here is how the try ... except ... construct works in detail:

First, the try clause (i.e. all the statements in the try block) are executed.

If no exception happens during the execution of the try clause, the except clause is completely ignored and the code continues as usual:

try:
    print("Code in the try clause")
    print(3 / 2)
    print("More code in the try clause")
except ZeroDivisionError:
    print("An exception happened")

print("Continue execution")
Code in the try clause
1.5
More code in the try clause
Continue execution

Note that the string "An exception happened" is never printed, because the try clause does not raise an exception and so the except clause is ignored.

If an exception does happen during the execution of the try clause, the rest of the try clause is skipped and the interpreter checks if the raised exception matches the exception after the except keyword. If that is the case, the except clause (i.e. the statements in the except block) is executed. Afterwards the code continues as usual:

try:
    print("Code in the try clause")
    print(3 / 0)
    print("More code in the try clause")
except ZeroDivisionError:
    print("An exception happened")

print("Continue execution")
Code in the try clause
An exception happened
Continue execution

Note that the string "More code in the try clause" is never printed, because an exception happens at the line print(3 / 0) and so the rest of the try clause after that line is skipped. Also note that because we caught the exception, the rest of the code continues as usual and so the line "Continue execution" is still printed.

If the raised exception does not match the exception after the except keyword, the except clause is ignored and the exception is still raised:

try:
    print("Code in the try clause")
    print(3 / 0)
    print("More code in the try clause")
except ValueError:
    print("An exception happened")

print("Continue execution")
Code in the try clause
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[5], line 3
      1 try:
      2     print("Code in the try clause")
----> 3     print(3 / 0)
      4     print("More code in the try clause")
      5 except ValueError:

ZeroDivisionError: division by zero

Note that the program crashes, so neither ""More code in the try clause" nor "Continue execution" are printed.

Handling Multiple Exceptions#

Consider this snippet:

user_value = "2"
user_value_int = int(user_value)
print(5 / user_value_int)
2.5

This may raise two exceptions - a ValueError in case user_value does not represent a valid integer and a ZeroDivisionError in case the user value is 0. We can handle both exceptions separately by writing multiple except clauses:

user_value = "2"

try:
    user_value_int = int(user_value)
    print(5 / user_value_int)
except ValueError:
    print(f"The user supplied a value of {user_value}, but the value must be an integer")
except ZeroDivisionError:
    print("The user supplied a value of 0")
2.5

Let’s try this out:

user_value = "2a"

try:
    user_value_int = int(user_value)
    print(5 / user_value_int)
except ValueError:
    print(f"The user supplied a value of {user_value}, but the value must be an integer")
except ZeroDivisionError:
    print("The user supplied a value of 0")
The user supplied a value of 2a, but the value must be an integer
user_value = "0"

try:
    user_value_int = int(user_value)
    print(5 / user_value_int)
except ValueError:
    print(f"The user supplied a value of {user_value}, but the value must be an integer")
except ZeroDivisionError:
    print("The user supplied a value of 0")
The user supplied a value of 0

We can also handle multiple exceptions at the same time by specifying a tuple of exceptions after the except keyword:

user_value = "2a"

try:
    user_value_int = int(user_value)
    print(5 / user_value_int)
except (ValueError, ZeroDivisionError):
    print(f"The user supplied a value of {user_value}, which is invalid")
The user supplied a value of 2a, which is invalid

Working with Exception Objects#

It is often useful to bind the exception to a variable by specifying a variable after the exception name:

try:
    print(1 / 0)
except ZeroDivisionError as e:
    print(type(e))
<class 'ZeroDivisionError'>

As you can see, the exception e is just a regular Python object of instance ZeroDivisionError! This is not just theoretically interesting - it means that we can work with attributes of that object. For example we can convert the exception to a string and print it:

try:
    print(1 / 0)
except ZeroDivisionError as e:
    print(str(e))
division by zero

This is very useful if we want to see what originally caused an error. It is in fact common practice to print the exception object when encountering an exception - especially during debugging.

Raising Exceptions#

You can also raise exceptions yourself by utilizing the raise statement:

raise ValueError("Bad value")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[13], line 1
----> 1 raise ValueError("Bad value")

ValueError: Bad value

This is useful if you want to indicate that a certain operation results in an error, which calling code may choose to handle.