I l@ve RuBoard Previous Section Next Section

7.2 Exception Basics

Python exceptions are a high-level control flow device. They may be raised either by Python or by our programs; in both cases, they may be caught by try statements. Python try statements come in two flavors—one that handles exceptions and one that executes finalization code whether exceptions occur or not.

7.2.1 try/except/else

The try is another compound statement; its most complete form is sketched below. It starts with a try header line followed by a block of indented statements, then one or more optional except clauses that name exceptions to be caught, and an optional else clause at the end:

try:
    <statements>        # run/call actions
except <name>:
    <statements>        # if 'name' raised during try block
except <name>, <data>:
   <statements>         # if 'name' raised; get extra data
else:
   <statements>         # if no exception was raised

Here's how try statements work. When a try statement is started, Python marks the current program context, so it can come back if an exception occurs. The statements nested under the try header are run first; what happens next depends on whether exceptions are raised while the try block's statements are running or not:

In other words, except clauses catch exceptions that happen while the try block is running, and the else clause is run only if no exceptions happen while the try block runs. The except clauses are very focused exception handlers; they catch exceptions that occur only within the statements in the associated try block. However, since the try block's statements can call functions elsewhere in a program, the source of an exception may be outside the try.

7.2.2 try/finally

The other flavor of the try statement is a specialization and has to do with finalization actions. If a finally clause is used in a try, its block of statements are always run by Python "on the way out," whether an exception occurred while the try block was running or not:

The try/finally form is useful when you want to be completely sure that an action happens after some code runs, regardless of the exception behavior of the program; we'll see an example in a moment. The finally clause can't be used in the same try statement as except and else, so they are best thought of as two different statements:

try:
    <statements>
finally:
    <statements>        # always run "on the way out"

7.2.3 raise

To trigger exceptions, you need to code raise statements. Their general form is simple: the word raise followed by the name of the exception to be raised. You can also pass an extra data item (an object) along with the exception, by listing it after the exception name. If extra data is passed, it can be caught in a try by listing an assignment target to receive it: except name, data:

raise <name>            # manually trigger an exception
raise <name>, <data>    # pass extra data to catcher too

So what's an exception name? It might be the name of a built-in exception from the built-in scope (e.g., IndexError), or the name of an arbitrary string object you've assigned in your program. It can also reference a class or class instance; this form generalizes raise statements, but we'll postpone this topic till later in this chapter. Exceptions are identified by objects, and at most one is active at any given time. Once caught by an except clause, an exception dies (won't propagate to another try), unless reraised by a raise or error.

7.2.4 First Examples

Exceptions are simpler than they seem. Since control flow through a program is easier to capture in Python than in English, let's look at some simple examples that illustrate exception basics.

7.2.4.1 Default behavior: Error messages

As mentioned, exceptions not caught by try statements reach the top level of a Python process and run Python's default exception-handling logic. By default, Python terminates the running program and prints an error message describing the exception, showing where the program was when the exception occurred. For example, running the following module generates a divide-by-zero exception; since the program ignores it, Python kills the program and prints:

% cat bad.py
def gobad(x, y):
    return x / y

def gosouth(x):
    print gobad(x, 0)

gosouth(1)

% python bad.py
Traceback (innermost last):
  File "bad.py", line 7, in ?
    gosouth(1)
  File "bad.py", line 5, in gosouth
    print gobad(x, 0)
  File "bad.py", line 2, in gobad
    return x / y
ZeroDivisionError: integer division or modulo

When an uncaught exception occurs, Python ends the program, and prints a stack trace and the name and extra data of the exception that was raised. The stack trace shows the filename, line number, and source code, for each function active when the exception occurred, from oldest to newest. For example, you can see that the bad divide happens at the lowest entry in the trace—line 2 of file bad.py, a return statement.

Because Python reports almost all errors at runtime by raising exceptions, exceptions are intimately bound up with the idea of error handling in general. For instance, if you've worked through the examples, you've almost certainly seen an exception or two along the way (even typos usually generate a SyntaxError exception). By default, you get a useful error display like the one above, which helps track down the problem. For more heavy-duty debugging jobs, you can catch exceptions with try statements.[3]

[3] You can also use Python's standard debugger, pdb, to isolate problems. Like C debuggers such as dbx and gdb, pdb lets you step through Python programs line by line, inspect variable values, set breakpoints, and so on. pdb is shipped with Python as a standard module and is written in Python. See Python's library manual or other Python texts for information on pdb usage.

7.2.4.2 Catching built-in exceptions

If you don't want your program terminated when an exception is raised by Python, simply catch it by wrapping program logic in a try. For example, the following code catches the IndexError Python raises when the list is indexed out of bounds (remember that list indexes are zero-based offsets; 3 is past the end):

def kaboom(list, n):
    print list[n]            # trigger IndexError

try:
    kaboom([0, 1, 2], 3)
except IndexError:           # catch exception here
    print 'Hello world!'

When the exception occurs in function kaboom, control jumps to the try statement's except clause, which prints a message. Since an exception is "dead" after it's been caught, the program continues past the whole try, rather than being terminated by Python. In effect, you process and ignore the error.

7.2.4.3 Raising and catching user-defined exceptions

Python programs can raise exceptions of their own too, using the raise statement. In their simplest form, user-defined exceptions are usually string objects, like the one MyError is assigned to in the following:

MyError = "my error"

def stuff(file):
    raise MyError

file = open('data', 'r')     # open an existing file
try:
    stuff(file)              # raises exception
finally:
    file.close()             # always close file

User-defined exceptions are caught with try statements just like built-in exceptions. Here, we've wrapped a call to a file-processing function in a try with a finally clause, to make sure that the file is always closed, whether the function triggers an exception or not. This particular function isn't all that useful (it just raises an exception!), but wrapping calls in try/finally statements is a good way to ensure that your closing-time activities always run.

I l@ve RuBoard Previous Section Next Section