Exception Objects

When Python raises an exception, it creates an object to hold information about what went wrong. Typically, this object will contain an error message, along with a filename and line number to help the programmer pinpoint the problem.

Different kinds of errors raise exceptions of different types, which allows a program to choose which kinds of errors to handle by specifying an exception type in the except. For example:

Download construct/excepttype.cmd

>>> for i in range(4): # one more than len(values) ... try:

... print 'reciprocal of', values[i], 'at', i, 'is', r

... except IndexError:

... except ArithmeticError:

... print 'unable to calculate reciprocal of', values[i]

unable to calculate reciprocal of 0

reciprocal of 1 at 2 is 1.0

index 3 out of range

Here, the first except block handles only indexing errors; arithmetic errors (such as dividing by zero) are handled by the second block. If we want to know exactly what went wrong, we must modify the except statement so that Python knows which variable to assign the exception object to:

Download construct/exceptobj.cmd

>>> for i in range(4): # one more than len(values) ... try:

... print 'reciprocal of', values[i], 'at', i, 'is', r

... except ArithmeticError, e:

... print 'error:' , e reciprocal of -1 at 0 is -1.0

error: float division reciprocal of 1 at 2 is 1.0

error: list index out of range

Python tests except blocks in order. Whichever matches first gets to handle the exception. The order matters because exceptions are arranged in a hierarchy, as shown in Figure 12.3, on the next page. This allows a program to handle ZeroDivisionErrors one way and all other Arith-meticErrors another, but only if the handler for the first comes before the handler for the second. An except that doesn't specify an exception class catches everything, so if there is one, it must appear last, like the else in an if-elif-else chain.

Figure 12.3: Exception hierarchy

Functions and Exceptions

What if Python can't find a matching exception handler in a particular try/except block? For example, what happens when Python tries to divide by zero in the following code?

Download construct/uncaught.py

for i in range(4): # one more than len(values) try:

print 'reciprocal of', va1ues[i], 'at', i, 'is', r except IndexError, e: print 'error:' , e

The answer is that Python keeps a stack of exception handlers, just like its stack of function calls (see Figure 12.4, on the following page). When an exception is raised, Python takes handlers off the exception handler stack one by one until it finds a handler that matches and then executes it. This means that the code to handle an exception can be a long way away from the place where the exception occurred. It also means that one exception handler can take care of exceptions from many pieces of code.

ZeroDivisionError try:

except IndexError: -

print 'Index out of range' except ArithmeticError:m-

average print 'Error with calculations'

IndexError?

ArithmeticError?

main

Figure 12.4: The exception handler stack

For example, suppose we are using several functions that calculate statistics on numbers stored in lists. Some of those functions might raise exceptions because of division by zero. We don't particularly care which function call is at fault, so we can deal with them all at once like this:

Download construct/exceptfunc.cmd

...other function definitions skipped...

values = read_values_from_file() try:

print 'average:', average(values) print 'median:', median(values) print 'standard deviation:', std_dev(values) except ArithmeticError:

print 'Error in calculations'

As you can see, the code that handles the error is outside the functions that are doing the work. This means that the functions can focus on doing their jobs and let whoever is calling them worry about how to handle mistakes.

Was this article helpful?

0 0

Post a comment