Exception handling is done with the
try
statement. The
try
statement encapsulates several
pieces of information. Primarily, it contains a suite of statements and
a group of exception-handling clauses. Each exception-handling clause
names a class of exceptions and provides a suite of statements to
execute in response to that exception.
The basic form of a
try
statement looks like
this:
except
exception
〈,
target
〉 :
suite
Each
suite
is an indented block of
statements. Any statement is allowed in the suite. While this means that
you can have nested
try
statements, that is rarely
necessary, since you can have an unlimited number of
except
clauses.
If any of the statements in the
try
suite raise
an exception, each of the
except
clauses are examined
to locate a clause that matches the exception raised. If no statement in
the
try
suite raises an exception, the
except
clauses are silently ignored.
The first form of the
except
clause provides a
specific exception class which is used for matching any exception which
might be raised. If a target
variable name is
provided, this variable will have the exception object assigned to
it.
The second form of the
except
clause is the
"catch-all" version. This will match all exceptions. If used, this must
be provided last, since it will always match the raised
exception.
We'll look at the additional
finally
clause in
a later sections.
The structure of the complete
try
statement
summarizes the philosophy of exceptions. First, try the suite of
statements, expecting them work. In the unlikely event that an exception
is raised, find an exception clause and execute that exception clause
suite to recover from or work around the exceptional situation.
Except clauses include some combination of error reporting,
recovery or work-around. For example, a recovery-oriented except clause
could delete useless files. A work-around exception clause could
returning a complex result for square root of a negative number.
First Example. Here's the first of several related examples. This will handle
two kinds of exceptions,
ZeroDivisionError
and
ValueError
.
Example 17.1. exception1.py
def avg( someList ):
"""Raises TypeError or ZeroDivisionError exceptions."""
sum= 0
for v in someList:
sum = sum + v
return float(sum)/len(someList)
def avgReport( someList ):
try:
m= avg(someList)
print "Average+15%=", m*1.15
except TypeError, ex:
print "TypeError: ", ex
except ZeroDivisionError, ex:
print "ZeroDivisionError: ", ex
This example shows the avgReport
function; it
contains a
try
clause that evaluates the
avg
function. We expect that there will be a
ZeroDivisionError
exception if an empty
list is provided to avg
. Also, a
TypeError
exception will be raised if the
list has any non-numeric value. Otherwise, it prints the average of the
values in the list.
In the
try
suite, we print the average. For
certain kinds of inappropriate input, we will print the exceptions which
were raised.
This design is generally how exception processing is handled. We
have a relatively simple, clear function which attempts to do the job in
a simple and clear way. We have a application-specific process which
handles exceptions in a way that's appropriate to the overall
application.
Nested
try
Statements. In more complex programs, you may have many function
definitions. If more than one function haa a
try
statement, the nested function evaluations will effectively nest the
try
statements inside each other.
This example shows a function solve
, which
calls another function, quad
. Both of these
functions have a
try
statement. An exception raised
by quad
could wind up in an exception handler in
solve
.
Example 17.2. exception2.py
def sum( someList ):
"""Raises TypeError"""
sum= 0
for v in someList:
sum = sum + v
return sum
def avg( someList ):
"""Raises TypeError or ZeroDivisionError exceptions."""
try:
s= sum(someList)
return float(s)/len(someList)
except TypeError, ex:
return "Non-Numeric Data"
def avgReport( someList ):
try:
m= avg(someList)
print "Average+15%=", m*1.15
except TypeError, ex:
print "TypeError: ", ex
except ZeroDivisionError, ex:
print "ZeroDivisionError: ", ex
In this example, we have the same avgReport
function, which uses avg
to compute an average of a
list. We've rewritten the avg
function to depend on
a sum
function. Both avgReport
and avg
contain
try
statements.
This creates a nested context for evaluation of exceptions.
Specifically, when the function sum is being evaluated, an
exception will be examined by avg
first, then
examined by avgReport
. For example, if
sum
raises a
TypeError
exception, it will be handled
by avg
; the avgReport
function
will not see the TypeError
exception.
Function Design - Exceptions or Status Codes. Note that this example has a subtle bug that illustrates an
important point regarding function design. We introduced the bug when
we defined avg
to return either an answer or an
error status code in the form of a string
.
Generally, things are more complex when we try to mix valid results
and error codes.
Status codes are the only way to report errors in languages that
lack exceptions. C, for example, makes heavy use of status codes. The
POSIX standard API definitions for operating system services are
oriented toward C. A program making OS requests must examing the results
to see if it is a proper values or an indication that an error occurred.
Python, however, doesn't have this limitation. Consequently many of the
OS functions available in Python modules will raise exceptions rather
than mix proper return values with status code values.
In our case, our design for avg
attepts to
return either a valid numeric result or a string result. To be correct
we would ave to do two kinds of error checking in
avgReport
. We would have to handle any exceptions
and we would also have to examine the results of
avg
to see if they are an error value or a proper
answer.
Rather than return status codes, a better design is to simply use
exceptions for all kinds of errors. IStatus codes have no real purposes
in well-designed programs. In the next section, we'll look at how to
define and raise our own exceptions.