As we’ve seen, raise
statements can pass an
extra data item along with the exception for use in a handler. In
general,
the extra data allows you to send context
information to a handler. In fact, every exception has the extra
data; much like function results, it’s the special
None
object if nothing was passed explicitly. The
following code illustrates:
myException = 'Error' # string object def raiser1(): raise myException, "hello" # raise, pass data def raiser2(): raise myException # raise, None implied def tryer(func): try: func() except myException, extraInfo: # run func, catch exception + data print 'got this:', extraInfo% python
>>>from helloexc import *
>>>tryer(raiser1)
# gets explicitly passed extra data got this: hello >>>tryer(raiser2)
# gets None by default got this: None
As a special case, Python 1.5 introduced an
assert
statement, which is mostly syntactic
shorthand for a raise
. A statement of the form:
assert <test
>, <data
> # the <data> part is optional
works like the following code:
if __debug__: if not <test
>: raise AssertionError, <data
>
but assert
statements may be removed from the
compiled program’s byte code if the -O
command-line flag is used, thereby optimizing the program.
Assertion-Error
is a built-in exception, and the _
_debug
__ flag is a built-in name which is
automatically set to 1 unless the -O
flag is used.
Assertions are typically used to verify program conditions during
development; when displayed, their message text includes source-code
line information automatically.
Recently, Python generalized the notion of exceptions. They may now also be identified by classes and class instances. Like module packages and private class attributes, class exceptions are an advanced topic you can choose to use or not. If you’re just getting started, you may want to mark this section as optional reading.
So far we’ve used strings to identify our own exceptions; when
raised, Python matches the exception to except
clauses based on object identity (i.e., using the
is
test we saw in Chapter 2).
But when a class exception is raised, an except
clause matches the current exception if it names the raised class or
a superclass of it. The upshot is that class exceptions support the
construction of exception hierarchies: by naming a general exception
superclass, an except
clause can catch an entire
category of exceptions; any more specific subclass will match.
In general, user-defined exceptions may be identified by string or
class objects. Beginning with Python 1.5, all built-in exceptions
Python may raise are predefined class objects, instead of strings.
You normally won’t need to care, unless you assume some
built-in exception is a string and try to concatenate it without
converting (e.g., KeyError
+
"spam"
, versus str(KeyError)
+ "spam"
).
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
is an addition in Python Version 1.5, which simply reraises the
current exception (it’s useful if you need to propagate an
arbitrary exception you’ve caught in a
except
block). 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).
raise string # matches except with same string object raise string, data # optional extra data (default=None) raise class, instance # matches except with this class, or a superclass of it raise instance # same as: raise instance.__class__, instance raise # re-raise the current exception (new in 1.5)
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 # all are really: raise instance raise class, (arg, arg,...)
These are all the same as saying raise
class(arg...)
, and therefore the same as the
raise instance
form above (Python calls the class
to create and raise an instance of it). 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.
If that sounds confusing, just remember that exceptions may be identified by string, class, or class instance objects, and you may pass extra data with the exception or not. If the extra data you pass with a class isn’t an instance object, Python makes an instance for you.
Let’s look at an example to see how class exceptions work. In
the following, we define a superclass General
and
one subclass of it called Specific
. We’re
trying to illustrate the notion of exception categories here;
handlers that catch General
will also catch a
subclass of it like Specific
. We then create
functions that raise instances of both classes as exceptions and a
top-level try
that catches
General
; the same try
catches
General
and Specific
exceptions, because Specific
is a subclass of
General
:
class General: pass
class Specific(General): pass
def raiser1():
X = General() # raise listed class instance
raise X
def raiser2():
X = Specific() # raise instance of subclass
raise X
for func in (raiser1, raiser2):
try:
func()
except General: # match General or any subclass of it
import sys
print 'caught:', sys.exc_type
% python classexc.py
caught: <class General at 881ee0>
caught: <class Specific at 881100>
Since there are only two possible exceptions here, this doesn’t
really do justice to the utility of class exceptions; we can achieve
the same effects by coding a list of string exception names in the
except
(e.g., except
(a,
b,
c):
),
and passing along an instance object as the extra data item. 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.
For example, the built-in exception
ArithmeticError
is a superclass to more specific
exceptions such as OverflowError
and
ZeroDivisionError
, but catching just
ArithmeticError
in a try
, you
catch any more specific kind of numeric error subclass raised.
Furthermore, if you add new kinds of numeric error subclasses in the
future, existing code that catches the
ArithmeticError
superclass (category) also catches
the new specific subclasses without modification; there’s no
need to explicitly extend a list of exception names.
Besides supporting hierarchies, class exceptions also provide storage
for extra state information (as instance attributes), but this
isn’t much more convenient than passing compound objects as
extra data with string exceptions (e.g., raise
string, object
). As usual in Python, the choice to
use OOP or not is mostly yours to make.