So far, we’ve been deliberately vague about what an exception actually is. Python generalizes the notion of exceptions—they may be identified by either string or class objects. Both have merits, but classes tend to provide a better solution when it comes to maintaining exception hierarchies.
In all the examples we’ve seen up to this point, user-defined exceptions have been strings. This is the simpler way to code an exception—any string value can be used to identify an exception:
>>>myexc = "My exception string"
>>>try:
...raise myexc
...except myexc:
...print 'caught'
... caught
Technically, the exception is identified by the string
object, not the string value—you must use
the same variable (i.e., reference) to raise and catch the exception
(we’ll expand on this idea in a gotcha at the
conclusion of Part VII). Here, the exception
name myexc
is just a normal variable—it can
be imported from a module, and so on. The text of the string is
almost irrelevant, except that it shows up in standard error
messages:
>>> raise myexc
Traceback (most recent call last):
File "<stdin>", line 1, in ?
My exception string
The text of the string exception here is printed as the exception message. If your string exceptions may print like this, you’ll want to use more meaningful text than most of the examples shown in this book.
Strings are a simple way to define your exceptions. Exceptions may also be identified with classes. Like some other topics we’ve met in this book, class exceptions are an advanced topic you can choose to use or not in Python 2.2. However, classes have some added value that merits a quick look; in particular, they allow us to identify exception categories that are more flexible to use and maintain than simple strings. Moreover, classes are likely to become the prescribed way to identify your exceptions in the future.
The chief difference between string and class exceptions has to do
with the way that exceptions raised are matched against
except
clauses in try
statements:
String exceptions are matched by simple object
identity: the raised exception is matched to
except
clauses by Python’s
is
test (not ==
).
Class exceptions are matched by superclass
relationships: the raised exception matches an
except
clause, if that except
clause names the exception’s class or any superclass
of it.
That is, when a try
statement’s
except
clause lists a superclass, it catches
instances of that superclass, as well as instances of all its
subclasses lower in the class tree. The net effect is that class
exceptions support the construction of exception hierarchies:
superclasses become category names, and
subclasses become specific kinds of exceptions within a category. By
naming a general exception superclass, an except
clause can catch an entire category of exceptions—any more
specific subclass will match.
Let’s look at an example to see how class exceptions
work in code. In the following file,
classexc.py, we define a superclass
General
and two subclasses of it called
Specific1
and Specific2
.
We’re illustrating the notion of exception
categories here: General
is a category name, and
its two subclasses are specific types of exceptions within the
category. Handlers that catch General
will also
catch any subclasses of it, including Specific1
and Specific2
.
class General: pass
class Specific1(General): pass
class Specific2(General): pass
def raiser0( ):
X = General( ) # Raise superclass instance.
raise X
def raiser1( ):
X = Specific1( ) # Raise subclass instance.
raise X
def raiser2( ):
X = Specific2( ) # Raise different subclass instance.
raise X
for func in (raiser0, raiser1, raiser2):
try:
func( )
except General: # Match General or any subclass of it.
import sys
print 'caught:', sys.exc_type
C:python> python classexc.py
caught: __main__.General
caught: __main__.Specific1
caught: __main__.Specific2
Notice that we call classes to make instances in
the raise
statements here; as
we’ll see when we formalize raise
statement forms later in this section, an instance is always present
when raising class-based exceptions. This code also includes
functions that raise instances of all three classes as exceptions,
and a top-level try
that calls the functions and
catches General
exceptions. The same
try
catches General
and the two
specific exceptions, because the two specific exceptions are
subclasses of General
.
Since there are only three possible exceptions in the prior
section’s example, it doesn’t
really do justice to the utility of class exceptions. In fact, we can
achieve the same effects by coding a list of string exception names
in parenthesis within the except
clause. File
stringexc.py shows how:
General = 'general'
Specific1 = 'specific1'
Specific2 = 'specific2'
def raiser0( ): raise General
def raiser1( ): raise Specific1
def raiser2( ): raise Specific2
for func in (raiser0, raiser1, raiser2):
try:
func( )
except (General, Specific1, Specific2): # Catch any of these.
import sys
print 'caught:', sys.exc_type
C:python> python stringexc.py
caught: general
caught: specific1
caught: specific2
But for large or high exception hierarchies, it may be easier to
catch categories using classes than to list every member of a
category in a single except
clause. Moreover,
exception hierarchies can be extended by adding new subclasses,
without breaking existing code.
Suppose you code a numeric programming library in Python, to be used by a large number of people. While you are writing your library, you identify two things that can go wrong with numbers in your code—division by zero, and numeric overflow. You document these as the two exceptions that your library may raise, and define them as simple strings in your code:
divzero = 'Division by zero error in library' oflow = 'Numeric overflow error in library' ... raise divzero
Now, when people use your library, they will typically wrap calls to
your functions or classes in try
statements that
catch your two exceptions (if they do not catch your exceptions,
exceptions from the library kill their code):
import mathlib ... try: mathlib.func(...) except (mathlib.divzero, mathlib.oflow): ...report and recover...
This works fine and people use your library. Six months down the road, you revise your library; along the way, you identify a new thing that can go wrong—underflow—and add that as a new string exception:
divzero = 'Division by zero error in library' oflow = 'Numeric overflow error in library' uflow = 'Numeric underflow error in library'
Unfortunately, when you rerelease your code, you’ve just created a maintenance problem for your users. Assuming they list your exceptions explicitly, they have to now go back and change every place they call your library, to include the newly added exception name:
try:
mathlib.func(...)
except (mathlib.divzero, mathlib.oflow, mathlib.uflow):
...report and recover...
Now, maybe this isn’t the end of the world. If your library is used only in-house, you can make the changes yourself. You might also ship a Python script that tries to fix such code automatically (it would be a few dozen lines, and would guess right at least some of the time). If many people have to change their code each time you alter your exceptions set, though, this is not exactly the most polite of upgrade policies.
Your users might try to avoid this pitfall by coding empty except clauses:
try: mathlib.func(...) except: # Catch everything here. ...report and recover...
The problem with this workaround is that it may catch more than they bargained for—even things like memory errors and system exits trigger exceptions, and you want such things to pass, not be caught and erroneously classified as a library error. As a rule of thumb, it’s usually better to be specific than general in exception handlers (an idea we’ll revisit in the gotchas).
So what to do, then? Class exceptions fix this dilemma completely. Rather than defining your library’s exceptions as a simple set of strings, arrange them into a class tree, with a common superclass to encompass the entire category:
class NumErr: pass class Divzero(NumErr): pass class Oflow(NumErr): pass ... raise DivZero( )
This way, users of your library simply need to list the common superclass (i.e., category), to catch all of your library’s exceptions—both now and in the future:
import mathlib ... try: mathlib.func(...) except mathlib.NumErr: ...report and recover...
When you go back and hack your code again, new exceptions are added as new subclasses of the common superclass:
class Uflow(NumErr): pass
The end result is that user code that catches your library’s exceptions will keep working, unchanged. In fact, you are then free to add, delete, and change your exceptions arbitrarily in the future—as long as clients name the superclass, they are insulated from changes in your exceptions set. In other words, class exceptions provide a better answer to maintenance issues than strings do. Class-based exceptions can also support state retention and inheritance in ways that strings cannot—a concept we’ll explore by example later in this section.
We didn’t really pull the prior section’s examples out of thin air. Although user-defined exceptions may be identified by string or class objects, all built-in exceptions that Python itself may raise are predefined class objects, instead of strings. Moreover, they are organized into a shallow hierarchy with general superclass categories and specific subclass types, much like the exceptions class tree in the prior section.
All the familiar exceptions you’ve seen (e.g.,
SyntaxError
) are really just predefined classes,
available both as built-in names (in module __builtin__
), and as attributes of the standard library module
exceptions
. In addition, Python organizes the
built-in exceptions into a hierarchy, to support a variety of
catching modes. For example:
Exception
Top-level root superclass of exceptions
StandardError
The superclass of all built-in error exceptions
ArithmeticError
The superclass of all numeric errors
OverflowError
A subclass that identifies a specific numeric error
And so on—you can read further about this structure in either
the library manual, or the help text of the
exceptions
module (see Chapter 11 for help on help
):
>>>import exceptions
>>>help(exceptions)
...lots of text omitted...
The built-in class tree allows you to choose how specific or general
your handlers will be. For example, the built-in exception
ArithmeticError
is a superclass to more specific
exceptions such as OverflowError
and
ZeroDivisionError
. By listing
ArithmeticError
in a try
, you
will catch any kind of numeric error raised; by listing just
OverflowError
, you will intercept just that
specific type of error, and no others.
Similarly, because StandardError
is the superclass
of all built-in error exceptions, you can generally use it to select
between built-in errors and user-defined exceptions in a
try
:
try: action( ) except StandardError: ...handle Python errors... except: ...handle user exceptions... else: ...handle no exception case...
You can also almost simulate an empty except
clause (that catches everything) by catching root class
Exception
, but not quite—string exceptions,
as well as standalone user-defined exceptions, are not subclasses of
the Exception
root class today.[1] Whether or not you will use categories in the built-in
class tree, it serves as a good example; by using similar techniques
for class exceptions in your own code, you can provide exception sets
that are flexible, and easily modified.
Other than this, built-in exceptions are largely indistinguishable
from strings. In fact, you normally don’t need to
care that they are classes, unless you assume built-in exception are
strings and try to concatenate without converting (e.g.,
KeyError+"spam
" fails, but
str(KeyError)+"spam
" works).
When we met string-based exceptions at the start of this section, we saw that the text of the string shows up in the standard error message when the exception is not caught. For an uncaught class exception, by default you get the class’s name, and a not very pretty display of the instance object that was raised:
>>>class MyBad: pass
>>>raise MyBad( )
Traceback (most recent call last): File "<pyshell#30>", line 1, in ? raise MyBad MyBad: <__main__.MyBad instance at 0x00B58980>
To do better, define the __repr__
or __str__
string representation overload methods in your
class, to return the string you want to display for your exception if
it reaches the default handler at the top:
>>>class MyBad:
...def __repr__(self):
...return "Sorry--my mistake!"
... >>>raise MyBad( )
Traceback (most recent call last): File "<pyshell#43>", line 1, in ? raise MyBad( ) MyBad: Sorry--my mistake!
The __repr__
overload method is called for
printing, and string conversion requests made to your
class’s instances. See Section 21.4 in Chapter 21.
Besides supporting flexible hierarchies,
class exceptions also provide storage
for extra state information as instance
attributes. When a class-based exception is raised, Python
automatically passes the class instance object along with the
exception, as the extra data item. As for string exceptions, you can
access the raised instance by listing an extra variable back in the
try
statement. This provides a natural hook for
supplying data and behavior to the handler.
Let’s demonstrate the notion of extra data by an example, and compare string and class-based approaches along the way. A program that parses datafiles might signal a formatting error by raising an exception instance that is filled out with extra details about the error:
>>>class FormatError:
...def __init__(self, line, file):
...self.line = line
...self.file = file
... >>>def parser( ):
...# when error found
...raise FormatError(42, file='spam.txt')
... >>>try:
...parser( )
...except FormatError, X:
...print 'Error at', X.file, X.line
... Error at spam.txt 42
In the except
clause here, variable
X
is assigned a reference to the instance that was
generated where the exception was raised. In practice, though, this
isn’t noticeably more convenient than passing
compound objects (e.g., tuples, lists, or dictionaries) as extra data
with string exceptions, and may not by itself be compelling enough to
warrant class-based exceptions:
>>>formatError = 'formatError'
>>>def parser( ):
...# when error found
...raise formatError, {'line':42, 'file':'spam.txt'}
... >>>try:
...parser( )
...except formatError, X:
...print 'Error at', X['file'], X['line']
... Error at spam.txt 42
This time, variable X
in the
except
clause is assigned the dictionary of extra
details listed at the raise
statement. The net
effect is similar, without having to code a class along the way. The
class approach might be more convenient, if the exception should also
have behavior—the exception class can also define
methods to be called in the handler:
class FormatError: def __init__(self, line, file): self.line = line self.file = file def logerror(self): log = open('formaterror.txt', 'a') print >> log, 'Error at', self.file, self.line def parser( ): raise FormatError(40, 'spam.txt') try: parser( ) except FormatError, exc: exc.logerror( )
In such a class, methods (like logerror
) may also
be inherited from superclasses, and instance
attributes (like line
and file
)
provide a place to save state for use in later
method calls. Here, we can mimic much of this effect by passing
simple functions in the string-based approach:
formatError = "formatError" def logerror(line, file): log = open('formaterror.txt', 'a') print >> log, 'Error at', file, line def parser( ): raise formatError, (41, 'spam.txt', logerror) try: parser( ) except formatError, data: data[2](data[0], data[1]) # Or simply: logerror( )
Naturally, such functions would not participate in inheritance like class methods do, and would not be able to retain state in instance attributes (lambdas and global variables are usually the best we can do for stateful functions). We could, of course, pass a class instance in the extra data of string-based exceptions to achieve the same effect. But if we go this far to mimic class-based exceptions, we might as well adopt them—we’d be coding a class anyhow.
In general, the choice between string- and class-based exceptions is much like the choice to use classes at all; not every program requires the power of OOP. Sting-based exceptions are a simpler tool for simpler tasks. Class-based exceptions become most useful for defining categories, and in advanced applications that can benefit from state retention and attribute inheritance. As usual in Python, the choice to use OOP or not is mostly yours to make (although this might change in a future release of Python).
With the
addition of class-based exceptions, the raise
statement can take the following five forms: the first two raise
string exceptions, the next two raise class exceptions, and the last
reraises the current exception (useful if you need to propagate an
arbitrary exception).
raise string # Matches except with same string object raise string, data # Pass optional extra data (default=None). raise instance # Same as: raise instance.__class__ instance. raise class, instance # Matches except with this class or its superclass raise # Reraise the current exception.
For class-based exceptions, Python always requires an instance of the
class. Raising an instance really raises the
instance’s class; the instance is passed along with
the class as the extra data item (it’s a good place
to store information for the handler). For backward compatibility
with Python versions in which built-in exceptions were strings, you
can also use these forms of the raise
statement:
raise class # Same as: raise class( ) raise class, arg # Same as: raise class(arg) raise class, (arg, arg,...) # Same as: raise class(arg, arg,...)
These are all the same as saying raise
class(arg...)
, and therefore the same as the raise
instance
form above. Specifically, if you list a class
instead of an instance, and the extra data item is not an instance of
the class listed, Python automatically calls the class with the extra
data items as constructor arguments to create and raise an instance
for you.
For example, you may raise an instance of the built-in
KeyError
exception by saying simply raise
KeyError
, even though KeyError is now a class; Python calls
KeyError
to make an instance along the way. In
fact, you can raise KeyError
, and any other
class-based exception, in a variety of ways:
raise KeyError( ) # Normal: raise an instance raise KeyError, KeyError( ) # Class, instance: uses instance raise KeyError # Class: instance will be generated raise KeyError, "bad spam" # Class, arg: instance is generated
For all of these raise
forms, a
try
statement of the form:
try: ... except KeyError, X: ...
assigns X
to the KeyError
instance raised.
If that sounds confusing, just remember that exceptions may be
identified by string or class instance objects. For strings, you may
pass extra data with the exception or not. For classes, if there is
no instance object in the raise
statement, Python
makes an instance for you.
[1] Note that current Python documentation says that
“It is recommended that user-defined class-based
exceptions be derived from the Exception class, although this is
currently not enforced.” That is,
Exception
subclasses are preferred to standalone
exception classes, but not required. The defensive programmer might
infer that it may be a good idea to adopt this policy anyhow.