I l@ve RuBoard |
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.
Clause Form |
Interpretation |
---|---|
except: |
Catch all (other) exception types |
except name: |
Catch a specific exception only |
except name, value: |
Catch exception and its extra data |
except (name1, name2): |
Catch any of the listed exceptions |
else: |
Run block if no exceptions raised |
finally: |
Always perform block |
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 trys 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 '\nTest 1:'; tester(2) print '\nTest 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).
I l@ve RuBoard |