DekGenius.com
[ Team LiB ] Previous Section Next Section

26.2 Exception Idioms

We've seen the mechanics behind exceptions. Now, let's take a look at some of the other ways they are typically used.

26.2.1 Exceptions Aren't Always Errors

In Python, all errors are exceptions, but not all exceptions are errors. For instance, we saw in Chapter 7 that file object read methods return empty strings at the end of a file. The built-in raw_input function that we first met in Chapter 3, and deployed in an interactive loop in Chapter 10, reads a line of text from the standard input stream (sys.stdin). Unlike file methods, raw_input raises the built-in EOFError at end of file, instead of returning an empty string (an empty string from raw_input means an empty line).

Despite its name, the EOFError exception is just a signal in this context, not an error. Because of this behavior, unless end-of-file should terminate a script, raw_input often appears wrapped in a try handler and nested in a loop, as in the following code.

while 1:
    try:
        line = raw_input(  )     # Read line from stdin.
    except EOFError:
        break                    # Exit loop at end of file
    else:
        ...process next line here...

Other built-in exceptions are similarly signals, not errors. Python also has a set of built-in exceptions that represent warnings, rather than errors. Some of these are used to signal use of deprecated (phased out) language features. See the standard library manual's description of built-in exceptions and the warnings module for more on warnings.

26.2.2 Functions Signal Conditions with raise

User-defined exceptions can also signal nonerror conditions. For instance, a search routine can be coded to raise an exception when a match is found, instead of returning a status flag that must be interpreted by the caller. In the following, the try/except/else exception handler does the work of an if/else return value tester:

Found = "Item found"

def searcher(  ):
    if ...success...:
        raise Found
    else:
        return

try:
    searcher(  )
except Found:              # Exception if item was found
    ...success...
else:                      # else returned: not found
    ...failure...

More generally, such a coding structure may also be useful for any function that cannot return a sentinel value to designate success or failure. For instance, if all objects are potentially valid return values, it's impossible for any return value to signal unusual conditions. Exceptions provide a way to signal results without a return value:

failure = "not found"

def searcher(  ):
    if ...success...:
        return ...founditem...
    else:
        raise failure

try:
    item = searcher(  )
except failure:
    ...report...
else:
    ...use item here...

Because Python is dynamically typed and polymorphic to the core, exceptions, rather than sentinel return values, are the generally preferred way to signal conditions.

26.2.3 Debugging with Outer try Statements

You can also make use of exception handlers to replace Python's default top-level exception-handling behavior. By wrapping an entire program (or a call to it) in an outer try in your top-level code, you can catch any exception that may occur while your program runs, thereby subverting the default program termination.

In the following, the empty except clause catches any uncaught exception raised while the program runs. To get hold of the actual exception that occurred, fetch the exc_type and exc_value attributes from the built-in sys module; they're automatically set to the current exception's name and extra data:[1]

[1] The built-in traceback module allows the current exception to be processed in a generic fashion, and a sys.exc_info( ) function returns a tuple containing the current exception's type, data, and traceback. sys.exc_type and sys.exc_value still work, but manage a single, global exception; exc_info( ) keeps track of each thread's exception information and so is thread-specific. This distinction matters only when using multiple threads in Python programs (a subject beyond this footnote's scope). See the Python library manual for more details.

try:
    ...run program...
except:                  # All uncaught exceptions come here.
    import sys
    print 'uncaught!', sys.exc_type, sys.exc_value

This structure is commonly used during development, to keep your program active even after errors occur—you can run additional tests without having to restart. It's also used when testing code, as described in the next section.

26.2.4 Running in-Process Tests

You might combine some of these coding patterns in a test-driver application, which tests other code within the same process:

import sys
log = open('testlog', 'a')
from testapi import moreTests, runNextTest, testName

def testdriver(  ):
    while moreTests(  ):
        try:
            runNextTest(  )
        except:
            print >> log, 'FAILED', testName(  ), sys.exc_type
        else:
            print >> log, 'PASSED', testName(  )

testdriver(  )

The testdriver function here cycles through a series of test calls (module testapi is left abstract in this example). Because an uncaught exception in a test case would normally kill this test driver, we need to wrap test case calls in a try if we want to continue the testing process after a test fails. As usual, the empty except catches any uncaught exception generated by a test case, and uses sys.exc_type to log the exception to a file; the else clause is run when no exception occurs—the test success case.

Such boilerplate code is typical of systems that test functions, modules, and classes, by running them in the same process as the test driver. In practice, testing can be much more sophisticated than this. For instance, to test external programs, we would instead check status codes or outputs generated by program launching tools such as os.system and os.popen, covered in the standard library manual; such tools do not generally raise exceptions for errors in the external program (in fact, the test cases may run in parallel with the test driver). At the end of this chapter, we'll also meet more complete testing frameworks provided by Python, such as doctest and PyUnit, which provide tools for comparing expected outputs with actual results.

Why You Will Care: Error Checks

One way to see why exceptions are useful is to compare coding styles in Python and languages without exceptions. For instance, if you want to write robust programs in the C language, you generally have to test return values or status codes after every operation that could possibly go astray:

doStuff(  )
{                                    # C program:
     if (doFirstThing(  ) == ERROR)  # Detect errors everywhere
          return ERROR;              # even if not handled here.
     if (doNextThing(  ) == ERROR)
          return ERROR;
     ...
     return doLastThing(  );
}

main(  )
{
     if (doStuff(  ) == ERROR)
          badEnding(  );
     else
          goodEnding(  );
}

In fact, realistic C programs often have as much code devoted to error detection as to doing actual work. But, in Python, you don't have to be so methodical; instead, you can wrap arbitrarily vast pieces of a program in exception handlers and write the parts that do the actual work to assume all is well:

def doStuff(  ):       # Python code
    doFirstThing(  )   # We don't care about exceptions here,
    doNextThing(  )    # so we don't need to detect them here.
    ...
    doLastThing(  )

if__name__ == '__main__':
    try:
        doStuff(  )    # This is where we care about results,
    except:            # so it's the only place we must check.
        badEnding(  )
    else:
        goodEnding(  )

Because control jumps immediately to a handler when an exception occurs, there's no need to instrument all your code to guard for errors. Moreover, because Python detects errors automatically, your code usually doesn't need to check for errors in the first place. The upshot is that exceptions let you largely ignore the unusual cases and avoid much error-checking code.


    [ Team LiB ] Previous Section Next Section