In [1]:
import numpy as np

Exceptions

An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.

You've already seen some exceptions in the Debugging lesson. *

Many programs want to know about exceptions when they occur. For example, if the input to a program is a file path. If the user inputs an invalid or non-existent path, the program generates an exception. It may be desired to provide a response to the user in this case.

It may also be that programs will generate exceptions. This is a way of indicating that there is an error in the inputs provided. In general, this is the preferred style for dealing with invalid inputs or states inside a python function rather than having an error return.

Catching Exceptions

Python provides a way to detect when an exception occurs. This is done by the use of a block of code surrounded by a "try" and "except" statement.


In [4]:
def divide(numerator, denominator):
    result = numerator/denominator
    print("result = %f" % result)

In [5]:
divide(1.0, 0)


--------------------------------------------------------------
ZeroDivisionError            Traceback (most recent call last)
<ipython-input-5-ebcb36a8aa31> in <module>
----> 1 divide(1.0, 0)

<ipython-input-4-2d5d8a17c73f> in divide(numerator, denominator)
      1 def divide(numerator, denominator):
----> 2     result = numerator/denominator
      3     print("result = %f" % result)

ZeroDivisionError: float division by zero

In [18]:
def divide1(numerator, denominator):
    try:
        GARBAGE
        result = numerator/denominator
        print("result = %f" % result)
    except (ZeroDivisionError, NameError) as err:
        import pdb; pdb.set_trace()
        print("You can't divide by 0! or use GARBAGE.")

In [19]:
divide1(1.0, 'a')


> <ipython-input-18-5b38ede8ee30>(8)divide1()
-> print("You can't divide by 0! or use GARBAGE.")
(Pdb) type(err)
<class 'NameError'>
(Pdb) isinstance(err, Exception)
True
(Pdb) exit()
--------------------------------------------------------------
NameError                    Traceback (most recent call last)
<ipython-input-18-5b38ede8ee30> in divide1(numerator, denominator)
      2     try:
----> 3         GARBAGE
      4         result = numerator/denominator

NameError: name 'GARBAGE' is not defined

During handling of the above exception, another exception occurred:

BdbQuit                      Traceback (most recent call last)
<ipython-input-19-a9166f13178f> in <module>
----> 1 divide1(1.0, 'a')

<ipython-input-18-5b38ede8ee30> in divide1(numerator, denominator)
      6     except (ZeroDivisionError, NameError) as err:
      7         import pdb; pdb.set_trace()
----> 8         print("You can't divide by 0! or use GARBAGE.")

<ipython-input-18-5b38ede8ee30> in divide1(numerator, denominator)
      6     except (ZeroDivisionError, NameError) as err:
      7         import pdb; pdb.set_trace()
----> 8         print("You can't divide by 0! or use GARBAGE.")

~/miniconda3/lib/python3.6/bdb.py in trace_dispatch(self, frame, event, arg)
     46             return # None
     47         if event == 'line':
---> 48             return self.dispatch_line(frame)
     49         if event == 'call':
     50             return self.dispatch_call(frame, arg)

~/miniconda3/lib/python3.6/bdb.py in dispatch_line(self, frame)
     65         if self.stop_here(frame) or self.break_here(frame):
     66             self.user_line(frame)
---> 67             if self.quitting: raise BdbQuit
     68         return self.trace_dispatch
     69 

BdbQuit: 

In [15]:
print(err)


--------------------------------------------------------------
NameError                    Traceback (most recent call last)
<ipython-input-15-8f0ccd491a61> in <module>
----> 1 print(err)

NameError: name 'err' is not defined

In [6]:
divide1(1.0, 2)


result = 0.500000

In [7]:
divide1("x", 2)


You can't divide by 0!

In [8]:
def divide2(numerator, denominator):
    try:
        result = numerator / denominator
        print("result = %f" % result)
    except (ZeroDivisionError, TypeError) as err:
        print("Got an exception: %s" % err)

In [9]:
divide2(1, "X")


Got an exception: unsupported operand type(s) for /: 'int' and 'str'

In [3]:
#divide2("x, 2)

Why didn't we catch this SyntaxError?


In [1]:
# Handle division by 0 by using a small number
SMALL_NUMBER = 1e-3
def divide3(numerator, denominator):
    try:
        result = numerator/denominator
    except ZeroDivisionError:
        result = numerator/SMALL_NUMBER
        print("result = %f" % result)
    except Exception as err:
        print("Different error than division by zero:", err)

In [12]:
divide3(1,0)


result = 1000.000000

In [13]:
divide3("1",0)


Different error than division by zero: unsupported operand type(s) for /: 'str' and 'int'

What do you do when you get an exception?

First, you can feel relieved that you caught a problematic element of your software! Yes, relieved. Silent fails are much worse. (Again, another plug for testing.)

Generating Exceptions

Why generate exceptions? (Don't I have enough unintentional errors?)


In [14]:
import pandas as pd
def validateDF(df):
    """"
    :param pd.DataFrame df: should have a column named "hours"
    """
    if not "hours" in df.columns:
        raise ValueError("DataFrame should have a column named 'hours'.")

In [15]:
df = pd.DataFrame({'hours': range(10) })
validateDF(df)

In [23]:
class SeattleCrimeError(Exception):
    pass

In [21]:
b = False
if not b:
    raise SeattleCrimeError("There's been a crime!")


--------------------------------------------------------------
SeattleCrimeError            Traceback (most recent call last)
<ipython-input-21-bd30635a6cac> in <module>
      1 b = False
      2 if not b:
----> 3     raise SeattleCrimeError("There's been a crime!")

SeattleCrimeError: There's been a crime!

Class exercise

For the entropy function, create a new functions that throws an exception if the argument is not a valid probability distribution.