9.2. Readings: exceptions#

9.2.1. What are exceptions?#

In all the previous chapters we have seen a lot of errors. All these errors are called exceptions. Exceptions are raised when unexpected situations occur during program execution. Some of these situations include division by zero, input that does not match what the program is expecting, trying to use variables that were removed from the execution session, giving wrong input to functions, etc.

Exceptions, if not handled, result in termination of program execution since the computer would not know how to resolve the situation. To prevent the program from terminating unexpectedly, there are ways to handle or catch those exceptions, as we will see below. This is really useful when we want to write a program that will not terminate, but will instead perform a different action or produce a meaningful error message when an error is encountered.

9.2.2. Exception Handling#

During exception handling, we instruct the computer what to do in case an exception happens. The default behavior is for the computer to stop executing the program. But if we write code to handle the exception, then it will execute that code and will not terminate. The syntax to do this is as follows:

try:
    code that we suspect might throw an exception
except Error:
    code to be executed when error occurs

As you can see from above the code block has two parts: the try and the except part. The try part specifies the block that will potentially raise an error, while in the except part we instruct the computer what to do in case an error occurs.

Let us have a look at the examples below. The code does the same thing: it divides a number with a list of numbers to find the quotients.

numerator = 15
denominators = [0,3,5]
for denominator in denominators:
    print(numerator/denominator)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[2], line 2
      1 for denominator in denominators:
----> 2     print(numerator/denominator)

ZeroDivisionError: division by zero

Since division by 0 does not make any sense, the program throws a ZeroDivisionError and exits immediately.

try:
    for denominator in denominators:
        print(numerator/denominator)
except ZeroDivisionError:
    print('Oops, you attempted to divide by zero! Program terminating...')
Oops, you attempted to divide by zero! Program terminating...

The error was raised again but this time the execution did not stop but went inside the except clause and executed the statement in the print() function. In this scenario it is usually said that the program finished executing gracefully.

Let’s see an example:

denominators_1 = [1, 2, 'hello']
try:
    for denominator in denominators_1:
        print(numerator/denominator)
except ZeroDivisionError:
    print('Oops, you attempted to divide by zero! Program terminating...')
except TypeError:
    print('Oops, an error has occurred!')
15.0
7.5
Oops, an error has occurred!

In this example, the error happened when the program tried to divide 15 by hello. Since this is not a ZeroDivisionError, the first except clause is ignored and the second one TypeException catches the error.

Note

In case you expect different types of errors to occur, then you can list them in except clauses one after the other. Also, in case you are not sure what type of error is thrown, you can specify Exception in the except clause and it will catch (nearly) any type of error thrown. However, this is not usually a good practice in case you would like to distinguish between different types of errors.

Note

If in a list of except clauses, you always put the Exception first, then the other exceptions will always be ignored. Imagine Exception as the set that contains most of the exceptions that can occur. Look at the example below. The code snippet in ZeroDivisionError can never be reached.

try:
    for denominator in denominators:
        print(numerator/denominator)
except Exception:
    print('Oops, an error has occurred!')
except ZeroDivisionError:
    print('Oops, you attempted to divide by zero! Program terminating...')
Oops, an error has occurred!

Warning

It is never a good idea to catch an Exception since it gives little information on what error occurred.

9.2.3. finally Clause#

The finally clause may be placed in a try block after the except statements. The code within the finally clause always executes regardless of whether an exception executes or not. The syntax looks like this:

try:
    code that we suspect might throw an exception
except ErrorType:
    code that handles the exception
finally:
    code to execute regardless of an exception happening

Usually, the finally clause contains code that would release resources so that they can be used later, even if an exception occurs. An example would be:

try:
    for denominator in denominators:
        print(numerator/denominator)
except ZeroDivisionError:
    print('Oops, you attempted to divide by zero!')
finally:
    print('Program terminating...')
Oops, you attempted to divide by zero!
Program terminating...

In this example, the exception was raised, so the print statement in the except block was executed and afterwards the control flow went in the finally block that will execute the print() statement there.

denominators_2 = [1,3,5]
try:
    for denominator in denominators_2:
        print(numerator/denominator)
except ZeroDivisionError:
    print('Oops, you attempted to divide by zero!')
finally:
    print('Program terminating...')
15.0
5.0
3.0
Program terminating...

In this case, no exception was raised, as a result the code block in the try statement was executed successfully. After it was finished, the control went to the finally block, printing the statement and terminating.

Note

You can create your own exceptions as well. In that case, to throw that exception from a try block you can use the raise exception_name statement and handle it in a corresponding except statement. However, usually it is recommended that you use the already built-in exceptions in Python. You can find more information about them in the documentation.