Learning Python - Mark Lutz [473]
In other words, where the program goes when an exception is raised depends entirely upon where it has been—it’s a function of the runtime flow of control through the script, not just its syntax. The propagation of an exception essentially proceeds backward through time to try statements that have been entered but not yet exited. This propagation stops as soon as control is unwound to a matching except clause, but not as it passes through finally clauses on the way.
Example: Control-Flow Nesting
Let’s turn to an example to make this nesting concept more concrete. The following module file, nestexc.py, 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:
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 re-raises
print('outer try')
% python nestexc.py
inner try
Notice, though, that the top-level module code at the bottom of the file 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 file. Python picks and runs just the most recent try with a matching except, which in this case is the try inside action1.
As I’ve mentioned, the place where an exception winds up jumping to depends on the control flow through the program at runtime. Because of this, to know where you will go, you need to know where you’ve been. In this case, where exceptions are handled is more a function of control flow than of statement syntax. However, we can also nest exception handlers syntactically—an equivalent case we’ll look at next.
Example: Syntactic Nesting
As I mentioned when we looked at the new unified try/except/finally statement in Chapter 33, it is possible to nest try statements syntactically by their position in your source code:
try:
try:
action2()
except TypeError: # Most recent matching try
print('inner try')
except TypeError: # Here, only if nested handler re-raises
print('outer try')
Really, this code just sets up the same handler-nesting structure as (and behaves identically to) the prior example. In fact, syntactic nesting works just like the cases sketched in Figures 35-1 and 35-2; the only difference is that the nested handlers are physically embedded in a try block, not coded in functions called elsewhere. For example, nested finally handlers all fire on an exception, whether they are nested syntactically or by means of the runtime flow through physically separated parts of your code:
>>> try:
... try:
... raise IndexError
... finally:
... print('spam')
... finally:
... print('SPAM')
...
spam
SPAM
Traceback (most recent call last):
File " IndexError See Figure 35-2 for a graphic illustration of this code’s operation; the effect is the same, but the function logic has been inlined as nested statements here. For a more useful example of syntactic nesting at work, consider the following file, except-finally.py: def raise1(): raise IndexError def noraise(): return def raise2(): raise SyntaxError for func in (raise1, noraise, raise2): print('\n', func, sep='') try: try: func() except IndexError: print('caught IndexError') finally: print('finally run') This code catches an exception if one is raised and performs a finally termination-time action regardless of whether an exception occurs. This may take a few moments to digest, but the effect is much like combining an except and a finally clause in a single try statement in Python 2.5 and later: % python except-finally.py caught IndexError finally run finally run finally run Traceback (most recent call last): File