< BACKMake Note | BookmarkCONTINUE >
156135250194107072078175030179198180024228156016206217188240240204175193246034064175168015

*Exceptions as Classes

As we mentioned above, as of Python 1.5, all standard exceptions are now identified using classes. User-defined, class-based exceptions have been around for longer than that (since Python 1.2!), but until 1.5, the standard exceptions remained implemented as strings, mostly for backwards compatibility. However, there are a number of advantages that classes bring to the table, and these reasons were what finally led to all standard exceptions being converted from strings to class-based.

Selection via Object Identity

The search for an exception handler (checking each except clause) is accomplished via object identity and not object value. That means that if you are using string exceptions, the string object used in the except clause must be the same as the string exception that is raised. Two different string objects, even if they contain exactly the same string, constitute different exceptions!

Using classes simplifies this selection mechanism because exception classes are, for the most part, static. When referring to an exception, you are really accessing a class object that is a built-in identifier and stays constant throughout the course of execution. Whether using IndexError in an except clause or in a raise statement, you can be sure that they are both referencing the same class object so that a corresponding handler will be found.

Relationship Between Exceptions

Utilizing classes also allows for a hierarchical structure of exceptions. There are two consequences of employing this construct:

Promotes Grouping of Related Exceptions

When errors were simply strings, there was no interrelationship between any pair of errors. Although most errors are unrelated, some are very closely related, such as IndexError—offset into a sequence with an invalid index, and KeyError—indexing into a map with an invalid key. String exceptions allow these exceptions to be related in context or description only and do not recognize any more than that codewise.

Class-based exceptions allow such a relationship. Both exceptions now are subclassed from a common ancestor, the LookupError exception. If your application defined a new class with a lookup-related error, it is now possible for you to create yet another related exception simply by also subclassing from LookupError, or even IndexError or KeyError.

The complete set of Python exceptions and class hierarchy can be found in Table 10.2

Simplifies Detection

With class-based exceptions, handler code can detect an entire exception class "tree" (i.e., an ancestor exception class as well as all derived subclasses). As an example, let us say that you just want to catch any general arithmetic error in your program. Our code may be structured something like the following:

							
try:
     code_to_scan_for_math_errors
except FloatingPointError:
     print "math exception found"
except ZeroDivisionError:
     print "math exception found"
except OverflowError:
     print "math exception found"

						

Since the handlers for each exception are the same, we can shorten the code to:

							
try:
     code_to_scan_for_math_errors
except (FloatingPointError, ZeroDivisionError, OverflowError):
    print "math exception found"

						

However, this solution is not as all-encompassing as it could be, is a little messy perhaps with all three exceptions listed, and does not take into account future expansion. What if the next version of Python comes with a new arithmetic exception, or perhaps you create such a new exception for your application? The code we have above would be out-of-date and inaccurate.

The solution is to reference a base class in your except clause. Because your new exceptions (as well as FloatingPointError, ZeroDivisionError, and OverflowError) are all subclassed from the ArithmeticError exception class, you can reference ArithmeticError which can then scan for all ArithmeticError exceptions as well as all exceptions derived from ArithmeticError. Updating our code one more time, we present the most flexible solution here:

							
try:
     code_to_scan_for_math_errors
except ArithmeticError:
     print "math exception found"

						

Now your code can handle all pre-existing ArithmeticError exceptions as well as any you may create subclassed from ArithmeticError. Care must be taken, however, when handling both classes and superclasses with the same try statement. Observe both of the following examples:

							
try:
     code_to_scan_for_math_errors
except ArithmeticError:
     print "math exception found"
except ZeroDivisionError:
     print "division by zero error"

try:
     code_to_scan_for_math_errors
except ZeroDivisionError:
     print "division by zero error"
except ArithmeticError:
     print "math exception found"

						

Exception handlers are mutually-exclusive, meaning that once a handler is found for an exception (or a base class), it is handled immediately without searching further. In the first example, a ZeroDivisionError will be handled only by the first except statement, producing an output of "math exception found." The except clause for ZeroDivisionError will not be reached.

The second example may prove to be more useful, as a specific arithmetic error (ZeroDivisionError) is handled first, leaving the general ArithmeticError handler to take care of any other exception derived from ArithmeticError.


Last updated on 9/14/2001
Core Python Programming, © 2002 Prentice Hall PTR

< BACKMake Note | BookmarkCONTINUE >

© 2002, O'Reilly & Associates, Inc.