Now that we’ve taken a first look, let’s fill in a few details behind Python’s exception model.
When you write
try
statements, a variety of clauses can
appear after the try
statement block; Table 7.1 summarizes all the possible forms. We’ve
already seen most of these in the previous examples—empty
except
clauses catch any exception,
finally
runs on the way out, and so on. There may
be any number of excepts
, but
finally
must appear by itself (without an
else
or except
), and there
should be only one else
in a
try
.
The fourth entry in Table 7.1 is new.
except
clauses can also provide a set of
exceptions to be caught, in parentheses; Python runs such a
clause’s statement block if any of the listed exceptions occur.
Since Python looks for a match within a given try
by inspecting except
clauses from top to bottom,
the parenthesized version is like listing each exception in its own
except
clause, except that the statement body
needs to be coded only once.
Here’s an example of multiple except
clauses
at work. In the following, when an exception is raised while the call
to the action
function is running, Python returns
to the try
and searches for the first
except
that catches the exception raised. It
inspects expect
clauses from top to bottom and
left to right, and runs the statements under the first that matches.
If none match, the exception is propagated past this
try
; the else
runs only when no
exception occurred. If you really want a catch-all clause, an empty
except
does the trick:
try: action() except NameError: ... except IndexError ... except KeyError: ... except (AttributeError, TypeError, SyntaxError): ... else: ...
So far, our examples have used only a single try
to catch exceptions, but what happens if one try
is physically nested inside another? For that
matter, what does it mean if a try
calls a
function that runs another try
? Both these cases
can be understood if you realize that Python stacks
try
statements at runtime. When an exception is
raised, Python returns to the most recently entered
try
statement with a matching
except
clause. Since each try
statement leaves a marker, Python can jump back to earlier
try
s by inspecting the markers stack.
An example will help make this clear. The following module defines
two functions; action2
is coded to trigger an
exception (you can’t add numbers and sequences), and
action1
wraps a call to action2
in a try
handler, to catch the exception. However,
the top-level module code at the bottom wraps a call to
action1
in a try
handler too.
When action2
triggers the
TypeError
exception, there will be two active
try
statements—the one in
action1
, and the one at the top level of the
module. Python picks the most recent (youngest) with a matching
except
, which in this case is the
try
inside action1
. In general,
the place where an exception winds up jumping to depends on the
control flow through a program at runtime:
def action2():
print 1 + [] # generate TypeError
def action1():
try:
action2()
except TypeError: # most recent matching try
print 'inner try'
try:
action1()
except TypeError: # here only if action1 reraises
print 'outer try'
% python nestexc.py
inner try
We’ve already talked about the
finally
clause, but here’s a more
sophisticated example. As we’ve seen, the
finally
clause doesn’t really catch specific
exceptions; rather, it taps into the exception propagation process.
When used, a finally
block is always executed on
the way out of a try
statement, whether the exit
is caused by an exception or normal completion of the statements in
the try
block. This makes
finally
blocks a good place to code clean-up
actions (like closing files, as in the previous example).
The next code snippet shows finally
in action with
and without exceptions. It defines two functions:
divide
, which may or may not trigger a
divide-by-zero error, and tester
, which wraps a
call to divide
in a try/finally
statement:
def divide(x, y):
return x / y # divide-by-zero error?
def tester(y):
try:
print divide(8, y)
finally:
print 'on the way out...'
print '
Test 1:'; tester(2)
print '
Test 2:'; tester(0) # trigger error
% python finally.py
Test 1:
4
on the way out...
Test 2:
on the way out...
Traceback (innermost last):
File "finally.py", line 11, in ?
print 'Test 2:'; tester(0)
File "finally.py", line 6, in tester
print divide(8, y)
File "finally.py", line 2, in divide
return x / y # divide-by-zero error?
ZeroDivisionError: integer division or modulo
Now, the module’s top-level code at the bottom calls
tester
twice:
The first call doesn’t generate an exception (8/2 works fine),
and the result (4) is printed. But the finally
clause’s block is run anyhow, so you get the on the way out
message.
The second call does generate an exception (8/0 is a very bad thing
to say). Control immediately jumps from the divide
function to the finally
block, and the message
prints again. However, Python continues propagating the exception,
which reaches the top level and runs the default exception action (a
stack trace).