DekGenius.com
[ Team LiB ] Previous Section Next Section

24.2 Exception Handling: The Short Story

Compared to some other core language topics we've met, exceptions are a fairly light-weight tool in Python. Because they are so simple, let's jump right into an initial example. Suppose you've coded the following function:

>>> def fetcher(obj, index):
...     return obj[index]

There's not much to this function—it simply indexes an object on a passed-in index. In normal operation, it returns the result of legal indexes:

>>> x = 'spam'
>>> fetcher(x, 3)           # Like x[3]
'm'

However, if you ask this function to index off the end of your string, you will trigger an exception when your function tries to run obj[index]. Python detects out-of-bounds sequence indexing, and reports it by raising (triggering) the built-in IndexError exception:

>>> fetcher(x, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 2, in fetcher
IndexError: string index out of range

Technically, because this exception is not caught by your code, it reaches the top level of the program and invokes the default exception handler—which simply prints the standard error message. By this point in the book, you've probably seen your share of standard error messages. They include the exception that was raised, along with a stack trace—a list of the lines and functions active when the exception occurred. When coding interactively, the file is just "stdin" (standard input stream) or "pyshell" (in IDLE), so file line numbers are not very meaningful here.

In a more realistic program launched outside the interactive prompt, the default handler at the top also terminates the program immediately. That course of action makes sense for simple scripts; errors often should be fatal, and the best you can do is inspect the standard error message. Sometimes this isn't what you want, though. Server programs, for instance, typically need to remain active even after internal errors. If you don't want the default exception behavior, wrap the call in a try statement to catch the exception yourself:

>>> try:
...     fetcher(x, 4)
... except IndexError:
...     print 'got exception'
...
got exception
>>>

Now, Python jumps to your handler (the block under the except clause that names the exception raised) automatically when the exception is triggered while the try block runs. When working interactively like this, after the except clause runs we wind up back at the Python prompt. In a more realistic program, try statements not only catch exceptions, but also recover from them:

>>> def catcher(  ):
...     try:
...         fetcher(x, 4)
...     except IndexError:
...         print 'got exception'
...     print 'continuing'
...
>>> catcher(  )
got exception
continuing
>>>

This time, after the exception is caught and handled, the program resumes execution after the entire try statement that caught it—which is why we get the "continuing" message here. You won't see the standard error message, and your program will continue on its way normally.

Exceptions can be raised both by Python and you, and can be caught or not. To trigger an exception manually, simply run a raise (or assert) statement; user-defined exceptions are caught the same way as built-ins:

>>> bad = 'bad'
>>> try:
...     raise bad
... except bad:
...     print 'got bad'
...
got bad

If not caught, user-defined exceptions reach the top-level default exception handler, and terminate your program with a standard error message. In this case, the standard message includes the text of the string used to identify the exception:

>>> raise bad
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in ?
    raise bad
bad

In other cases, the error message may include text provided by classes used to identify exceptions. As we'll see in the next chapter, class-based exceptions allow scripts to build exception categories (and other things):

>>> class Bad: pass
...
>>> def doomed(  ): raise Bad(  )
...
>>> try:
...     doomed(  )
... except Bad:
...     print 'got Bad'
...
got Bad
>>>

Finally, try statements can also say finally: the try/finally combination specifies termination actions that always execute "on the way out"—whether an exception happens in the try block or not:

>>> try:
...     fetcher(x, 3)
... finally:
...     print 'after fetch'
...
'm'
after fetch

Here, when the try block finishes without an exception, the finally block runs, and then the program resumes after the entire try. In this case, this statement seems a bit silly—we might as well have simply typed the print right after a call to the function, and skipped try altogether:

fetcher(x, 3)
print 'after fetch'

There is a problem with coding this way, though: if the function call raises an exception, we will never reach the print. The try/finally lets us do better—when an exception does occur in the try block, finally blocks are executed while the program is being unwound:

>>> def after(  ):
...     try:
...         fetcher(x, 4)
...     finally:
...         print 'after fetch'
...     print 'after try?'
...
>>> after(  )
after fetch
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in after
  File "<stdin>", line 2, in fetcher
IndexError: string index out of range

Here, we didn't get the "after try?" message, because control does not resume after the try/finally when an exception occurs. Instead, Python jumps back to run the finally action, but then keeps propagating the exception to a prior handler (in this case, to the default handler at the top). If you change the call inside this function so as not to trigger an exception, the finally code still runs, but the program continues after the try:

>>> def after(  ):
...     try:
...         fetcher(x, 3)
...     finally:
...         print 'after fetch'
...     print 'after try?'
...
>>> after(  )
after fetch
after try?
>>>

In practice, try/except combinations are useful for catching and recovering from exceptions, and try/finally comes in handy to guarantee that termination actions will fire regardless of the exceptions that may occur in the try block's code. For instance, you might use try/except to catch errors raised by code that you import from a third-party library, and try/finally to ensure that calls to close files or terminate server connections are always run. We'll see some such practical examples later in this part of the book.

That is a majority of the exception story; it really is a simple tool. In the rest of this part, we'll fill in some of the details of the statements involved, show you the other sorts of clauses that can appear under a try, and discuss string and class-based exception objects.

Python exceptions are a high-level control flow device. They may be raised either by Python or by your own programs; in both cases, they may be ignored (to trigger the default error message), or may be caught by try statements (to be processed by your code). Python's raise and assert statements trigger exceptions on demand. The try statement comes in two formats—one that handles exceptions and one that executes finalization code whether exceptions occur or not. Let's take a deeper look at these statements' general forms.

    [ Team LiB ] Previous Section Next Section