[ Team LiB ] |
26.2 Exception IdiomsWe'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 ErrorsIn 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 raiseUser-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 StatementsYou 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]
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 TestsYou 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.
|
[ Team LiB ] |