I l@ve RuBoard |
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.
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:
If an exception occurs while the try block's statements are running, Python jumps back to the try and runs the statements under the first except clause that matches the raised exception. Control continues past the entire try statement after the except block runs (unless the except block raises another exception).
If an exception happens in the try block and no except clause matches, the exception is propagated up to a try that was entered earlier in the program, or to the top level of the process (which makes Python kill the program and print a default error message).
If no exception occurs while the statements under the try header run, Python runs the statements under the else line (if present), and control then resumes past the entire try statement.
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.
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:
If no exception occurs, Python runs the try block, then the finally block, and then continues execution past the entire try statement.
If an exception does occur during the try block's run, Python comes back and runs the finally block, but then propagates the exception to a higher try (or the top level); control doesn't continue past the try statement.
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"
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.
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.
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.
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.
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 |