Exceptions are more sophisticated than assertions. They are the standard error messaging system in most modern programming languages. Fundamentally, when an error is encountered, an informative exception is ‘thrown’ or ‘raised’.
For example, instead of the assertion in the case before, an exception can be used. Add the following function to your mean.py
script:
def mean_exc(num_list):
if len(num_list) == 0 :
raise Exception("The arithmetic mean of an empty list is undefined.\
Please provide a list of numbers")
else :
return sum(num_list)/len(num_list)
Now import the library in ipython3 and examine its behaviour by creating an empty list and calling the new function.
In [1]: from mean import *
In [2]: nonumbers = []
In [3]: mean_exc(nonumbers)
Your output should look something like:
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-3-ffe1be2cdd57> in <module>()
----> 1 mean_exc(nonumbers)
/u/q/rjg20/intro-testing/my_testing/mean.py in mean_exc(num_list)
15 if len(num_list) == 0 :
16 raise Exception("The arithmetic mean of an empty list is undefined.\
---> 17 Please provide a list of numbers")
18 else :
19 return sum(num_list)/len(num_list)
Exception: The algebraic mean of an empty list is undefined.
Please provide a list of numbers
try
afterall¶If we are confident in the behaviour of our code, rather than immediately halting code execution, the exception can be ‘caught’ upstream with a try-except block. When wrapped in a try-except block, the exception can be intercepted before it reaches global scope and halts execution.
To add information or replace the message before it is passed upstream, the try-catch block can be used to catch-and-reraise the exception:
def mean_try(num_list):
try:
return sum(num_list)/len(num_list)
except ZeroDivisionError as detail :
msg = "The arithmetic mean of an empty list is undefined. Please provide a list of numbers."
raise ZeroDivisionError(detail.__str__() + "\n" + msg)
Alternatively, the exception can simply be handled intelligently. If an alternative behavior is preferred, the exception can be disregarded and a responsive behavior can be implemented like so:
def mean_try_again(num_list):
try:
return sum(num_list)/len(num_list)
except ZeroDivisionError :
return 0
How does this new function treat an empty list? Note however that this may not be the behaviour we want. If the list is empty then it may be that we don’t want the mean to be defined.
If a single function might raise more than one type of exception, each can be caught and handled separately. Modify your new function to include a second exception case:
def mean_try_again(num_list):
try:
return sum(num_list)/len(num_list)
except ZeroDivisionError :
return 0
except TypeError as detail :
msg = "The arithmetic mean of an non-numerical list is undefined.\
Please provide a list of numbers."
raise TypeError(detail.__str__() + "\n" + msg)
Beware however that handling exceptions 'intelligently' requires us to have confidence in the behaviour of our code and users. It also requires us to identify each possible type of Exception
, whereas in the earlier cases we handle these and provide a meaningful message by default. We will see later with so-called 'edge' and 'corner' cases that exceptions could be raised in many ways beyond our ability to identify them.