6.2 Exception Propagation

When an exception is raised, the exception-propagation mechanism takes control. The normal control flow of the program stops, and Python looks for a suitable exception handler. Python's try statement establishes exception handlers via its except clauses. The handlers deal with exceptions raised in the body of the try clause, as well as exceptions that propagate from any of the functions called by that code, directly or indirectly. If an exception is raised within a try clause that has an applicable except handler, the try clause terminates and the handler executes. When the handler finishes, execution continues with the statement after the try statement.

If the statement raising the exception is not within a try clause that has an applicable handler, the function containing the statement terminates, and the exception propagates upward to the statement that called the function. If the call to the terminated function is within a try clause that has an applicable handler, that try clause terminates, and the handler executes. Otherwise, the function containing the call terminates, and the propagation process repeats, unwinding the stack of function calls until an applicable handler is found.

If Python cannot find such a handler, by default the program prints an error message to the standard error stream (the file sys.stderr). The error message includes a traceback that gives details about functions terminated during propagation. You can change Python's default error-reporting behavior by setting sys.excepthook (covered in Chapter 8). After error reporting, Python goes back to the interactive session, if any, or terminates if no interactive session is active. When the exception class is SystemExit, termination is silent and includes the interactive session, if any.

Here are some functions that we can use to see exception propagation at work.

def f(  ):
    print "in f, before 1/0"
    1/0                           # raises a ZeroDivisionError exception
    print "in f, after 1/0"

def g(  ):
    print "in g, before f(  )"
    f(  )
    print "in g, after f(  )"

def h(  ):
    print "in h, before g(  )"
        g(  )
        print "in h, after g(  )"
    except ZeroDivisionError:
        print "ZD exception caught"
    print "function h ends"

Calling the h function has the following results:

>>> h(  )
in h, before g(  )
in g, before f(  )
in f, before 1/0
ZD exception caught
function h ends

Function h establishes a try statement and calls function g within the try clause. g, in turn, calls f, which performs a division by 0, raising an exception of class ZeroDivisionError. The exception propagates all the way back to the except clause in h. Functions f and g terminate during the exception propagation phase, which is why neither of their "after" messages is printed. The execution of h's try clause also terminates during the exception propagation phase, so its "after" message isn't printed either. Execution continues after the handler, at the end of h's try/except block.

    Part III: Python Library and Extension Modules