DekGenius.com
[ Team LiB ] Previous Section Next Section

26.1 Nesting Exception Handlers

Our examples so far 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? Technically, try statements can nest in terms of both syntax, and the runtime control flow through your code.

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 stacked. This nesting of active handlers is what we mean by "higher" handlers—try statements entered earlier in the program's execution flow.

For example, Figure 26-1 illustrates what occurs when try/except statements nest at runtime. Because the amount of code that can go into a try clause block can be substantial (e.g., function calls), it will typically invoke other code that may be watching for the same exception. When the exception is eventually raised, Python jumps back to the most recently entered try statement that names that exception, runs that statement's except clauses, and then resumes after that try.

Figure 26-1. nested try/except
figs/lpy2_2601.gif

Once the exception is caught, its life is over—control does not jump back to all matching trys that names the exception, just one. In Figure 26-1, for instance, the raise in function func2 sends control back to the handler in func1, and then the program continues within func1.

By contrast, when try/finally statements are used, control runs the finally block on exceptions, but then continues propagating the exception to other trys, or to the top-level default handler (standard error message printer). As Figure 26-2 illustrates, the finally clauses do not kill the exception—they just specify code to be run on the way out, during the exception propagation process. If there are many try/finally clauses active when an exception occurs, they will all be run (unless a try/except catches the exception somewhere along the way).

Figure 26-2. nested try/finally
figs/lpy2_2602.gif

26.1.1 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 reraises.
    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. Python picks and runs just the most recent 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. In other words, to know where you will go, you need to know where you've been—exceptions are more a function of control flow than statement syntax.

26.1.2 Example: Syntactic Nesting

It's also possible to nest try statements syntactically:

try:
    try:
        action2(  )
    except TypeError:      # Most recent matching try
        print 'inner try'
except TypeError:          # Here, only if nested handler reraises.
    print 'outer try'

But really, this just sets up the same handler nesting structure as, and behaves identical to, the prior example. In fact, syntactic nesting works just like the cases we sketched in Figures Figure 26-1 and Figure 26-2. The only difference is that the nested handlers are physically embedded in a try block, not coded in a called function elsewhere. For example, nested finally handlers all fire on an exception, whether they are nested syntactically, or by 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 "<stdin>", line 3, in ?
IndexError

See Figure 26-2 for a graphic illustration of this code's operation; it's the same effect, but 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
    try:
        try:
            func(  )
        except IndexError:
            print 'caught IndexError'
    finally:
        print 'finally run'

This code catches an exception if it is raised, and performs a finally termination time action regardless of whether any exception occurred or not. This takes a few moments to digest, but the effect is much like combining an except and finally clause in a single try statement, even though such a combination would be syntactically illegal (they are mutually exclusive):

% python except-finally.py

<function raise1 at 0x00867DF8>
caught IndexError
finally run

<function noraise at 0x00868EB8>
finally run

<function raise2 at 0x00875B80>
finally run
Traceback (most recent call last):
  File "except-finally.py", line 9, in ?
    func(  )
  File "except-finally.py", line 3, in raise2
    def raise2(  ): raise SyntaxError
SyntaxError
    [ Team LiB ] Previous Section Next Section