Chapter 10. Errors and Exceptions

Errors are an everyday occurrence in the life of a programmer. In days hopefully long since past, errors were either fatal to the program (or perhaps the machine) or produced garbage output that was not recognized as valid input by other computers or programs or by the humans who submitted the job to be run. Any time an error occurred, execution was halted until the error was corrected and code was re-executed. Over time, demand surged for a “softer” way of dealing with errors other than termination. Programs evolved such that not every error was malignant, and when they did happen, more diagnostic information was provided by either the compiler or the program during runtime to aid the programmer in solving the problem as quickly as possible. However, errors are errors, and any resolution usually took place after the program or compilation process was halted. There was never really anything a piece of code could do but exit and perhaps leave some crumbs hinting at a possible cause—until exceptions and exception handling came along.

Although we have yet to cover classes and object-oriented programming in Python, many of the concepts presented here involve classes and class instances.1 We conclude the chapter with an optional section on how to create your own exception classes.

1As of Python 1.5, all standard exceptions are implemented as classes. If new to classes, instances, and other object-oriented terminology, the reader should see Chapter 13 for clarification.

This chapter begins by exposing the reader to exceptions, exception handling, and how they are supported in Python. We also describe how programmers can generate exceptions within their code. Finally, we reveal how programmers can create their own exception classes.

10.1 What Are Exceptions?

10.1.1 Errors

Before we get into detail about what exceptions are, let us review what errors are. In the context of software, errors are either syntactical or logical in nature. Syntax errors indicate errors with the construct of the software and cannot be executed by the interpreter or compiled correctly. These errors must be repaired before execution can occur.

Once programs are semantically correct, the only errors that remain are logical. Logical errors can either be caused by lack of or invalid input, or, in other cases, by the inability of the logic to generate, calculate, or otherwise produce the desired results based on the input. These errors are sometimes known as domain and range failures, respectively.

When errors are detected by Python, the interpreter indicates that it has reached a point where continuing to execute in the current flow is no longer possible. This is where exceptions come into the picture.

10.1.2 Exceptions

Exceptions can best be described as action that is taken outside of the normal flow of control because of errors. This action comes in two distinct phases: The first is the error that causes an exception to occur, and the second is the detection (and possible resolution) phase.

The first phase takes place when an exception condition (sometimes referred to as exceptional condition) occurs. Upon detection of an error and recognition of the exception condition, the interpreter performs an operation called raising an exception. Raising is also known as triggering, throwing, or generating, and is the process whereby the interpreter makes it known to the current control flow that something is wrong. Python also supports the ability of the programmer to raise exceptions. Whether triggered by the Python interpreter or the programmer, exceptions signal that an error has occurred. The current flow of execution is interrupted to process this error and take appropriate action, which happens to be the second phase.

The second phase is where exception handling takes place. Once an exception is raised, a variety of actions can be invoked in response to that exception. These can range anywhere from ignoring the error, to logging the error but otherwise taking no action, performing some corrective measures and aborting the program, or alleviating the problem to allow for resumption of execution. Any of these actions represents a continuation, or an alternative branch of control. The key is that the programmer can dictate how the program operates when an error occurs.

As you may have already concluded, errors during runtime are primarily caused by external reasons, such as poor input, a failure of some sort, etc. These causes are not under the direct control of the programmer, who can anticipate only a few of the errors and code the most general remedies.

Languages like Python, which support the raising and—more importantly—the handling of exceptions, empower the developer by placing them in a more direct line of control when errors occur. The programmer not only has the ability to detect errors, but also to take more concrete and remedial actions when they occur. Due to the ability to manage errors during runtime, application robustness is increased.

Exceptions and exception handling are not new concepts. They are also present in Ada, Modula-3, C++, Eiffel, and Java. The origins of exceptions probably come from operating systems code that handles exceptions such as system errors and hardware interruptions. Exception handling as a software tool made its debut in the mid-1950s with Lisp being the first major programming language that featured exceptions, followed a decade later by PL/1. Like some of the other languages supporting exception handling, Python is endowed with the concepts of a “try” block and “catching” exceptions and, in addition, provides for more “disciplined” handling of exceptions. By this we mean that you can create different handlers for different exceptions, as opposed to a general “catch-all” code where you may be able to detect the exception that occurred in a post-mortem fashion.

10.2 Exceptions in Python

As you were going through some of the examples in the previous chapters, you no doubt noticed what happens when your program “crashes” or terminates due to unresolved errors. A “traceback” notice appears along with a notice containing as much diagnostic information as the interpreter can give you, including the error name, reason, and perhaps even the line number near or exactly where the error occurred. All errors have a similar format, regardless of whether running within the Python interpreter or standard script execution, providing a consistent error interface. All errors, whether they be syntactical or logical, result from behavior incompatible with the Python interpreter and cause exceptions to be raised.

Let us take a look at some exceptions now.

NameError: attempt to access an undeclared variable

image

NameError indicates access to an uninitialized variable. The offending identifier was not found in the Python interpreter’s symbol table. We will be discussing namespaces in the next two chapters, but as an introduction, regard them as “address books” linking names to objects. Any object that is accessible should be listed in a namespace. Accessing a variable entails a search by the interpreter, and if the name requested is not found in any of the namespaces, a NameError exception will be generated.

ZeroDivisionError: division by any numeric zero

image

Our example above used integers, but in general, any numeric division-by-zero will result in a ZeroDivisionError exception.

SyntaxError: Python interpreter syntax error

image

SyntaxError exceptions are the only ones that do not occur at runtime. They indicate an improperly constructed piece of Python code which cannot execute until corrected. These errors are generated at compile-time, when the interpreter loads and attempts to convert your script to Python bytecode. These may also occur as a result of importing a faulty module.

IndexError: request for an out-of-range index for sequence

image

IndexError is raised when attempting to access an index that is outside the valid range of a sequence.

KeyError: request for a non-existent dictionary key

image

Mapping types such as dictionaries depend on keys to access data values. Such values are not retrieved if an incorrect/nonexistent key is requested. In this case, a KeyError is raised to indicate such an incident has occurred.

IOError: input/output error

image

Attempting to open a nonexistent disk file is one example of an operating system input/output (I/O) error. Any type of I/O error raises an IOError exception.

AttributeError: attempt to access an unknown object attribute

image

In our example, we stored a value in myInst.bar, the bar attribute of instance myInst. Once an attribute has been defined, we can access it using the familiar dotted-attribute notation, but if it has not, as in our case with the foo (non-)attribute, an AttributeError occurs.

10.3 Detecting and Handling Exceptions

Exceptions can be detected by incorporating them as part of a try statement. Any code suite of a try statement will be monitored for exceptions.

There are two main forms of the try statement: try-except and try-finally. These statements are mutually exclusive, meaning that you pick only one of them. A try statement can be accompanied by one or more except clauses, exactly one finally clause, or a hybrid try-except-finally combination.

try-except statements allow one to detect and handle exceptions. There is even an optional else clause for situations where code needs to run only when no exceptions are detected. Meanwhile, try-finally statements allow only for detection and processing of any obligatory cleanup (whether or not exceptions occur), but otherwise have no facility in dealing with exceptions. The combination, as you might imagine, does both.

10.3.1 try-except Statement

The try-except statement (and more complicated versions of this statement) allows you to define a section of code to monitor for exceptions and also provides the mechanism to execute handlers for exceptions.

The syntax for the most general try-except statement is given below. It consists of the keywords along with the try and except blocks (try_suite and except_suite) as well as optionally saving the reason of failure:

image

Starting with Python 3.0, the comma is changed to as. For the remaining 2.x releases starting with 2.6, Python supports both the comma and as, e.g., “except Exception as reason“ is equivalent to the sample syntax above. For more information, please see PEP 3110.

image

Let us give one example, then explain how things work. We will use our IOError example from above. We can make our code more robust by adding a try-except “wrapper” around the code:

image

As you can see, our code now runs seemingly without errors. In actuality, the same IOError still occurred when we attempted to open the nonexistent file. The difference? We added code to both detect and handle the error. When the IOError exception was raised, all we told the interpreter to do was to output a diagnostic message. The program continues and does not “bomb out” as our earlier example—a minor illustration of the power of exception handling. So what is really happening codewise?

During runtime, the interpreter attempts to execute all the code within the try statement. If an exception does not occur when the code block has completed, execution resumes past the except statement. When the specified exception named on the except statement does occur, we save the reason, and control flow immediately continues in the handler (all remaining code in the try clause is skipped) where we display our error message along with the cause of the error.

In our example above, we are catching only IOError exceptions. Any other exception will not be caught with the handler we specified. If, for example, you want to catch an OSError, you have to add a handler for that particular exception. We will elaborate on the try-except syntax more as we progress further in this chapter.

Core Note: Skipping code, continuation, and upward propagation

image

The remaining code in the try suite from the point of the exception is never reached (hence never executed). Once an exception is raised, the race is on to decide on the continuing flow of control. The remaining code is skipped, and the search for a handler begins. If one is found, the program continues in the handler.

If the search is exhausted without finding an appropriate handler, the exception is then propagated to the caller’s level for handling, meaning the stack frame immediately preceding the current one. If there is no handler at the next higher level, the exception is yet again propagated to its caller. If the top level is reached without an appropriate handler, the exception is considered unhandled, and the Python interpreter will display the traceback and exit.

10.3.2 Wrapping a Built-in Function

We will now present an interactive example—starting with the bare necessity of detecting an error, then building continuously on what we have to further improve the robustness of our code. The premise is in detecting errors while trying to convert a numeric string to a proper (numeric object) representation of its value.

The float() built-in function has a primary purpose of converting any numeric type to a float. In Python 1.5, float() was given the added feature of being able to convert a number given in string representation to an actual float value, obsoleting the use of the atof() function of the string module. Readers with older versions of Python may still use string.atof(), replacing float(), in the examples we use here.

image

Unfortunately, float() is not very forgiving when it comes to bad input:

image

Notice in the errors above that float() does not take too kindly to strings that do not represent numbers or non-strings. Specifically, if the correct argument type was given (string type) but that type contained an invalid value, the exception raised would be ValueError because it was the value that was improper, not the type. In contrast, a list is a bad argument altogether, not even being of the correct type; hence, TypeError was thrown.

Our exercise is to call float() “safely,” or in a more “safe manner,” meaning that we want to ignore error situations because they do not apply to our task of converting numeric string values to floating point numbers, yet are not severe enough errors that we feel the interpreter should abandon execution. To accomplish this, we will create a “wrapper” function, and, with the help of try-except, create the environment that we envisioned. We shall call it safe_float(). In our first iteration, we will scan and ignore only ValueErrors, because they are the more likely culprit. TypeErrors rarely happen since somehow a non-string must be given to float().

image

The first step we take is to just “stop the bleeding.” In this case, we make the error go away by just “swallowing it.” In other words, the error will be detected, but since we have nothing in the except suite (except the pass statement, which does nothing but serve as a syntactical placeholder for where code is supposed to go), no handling takes place. We just ignore the error.

One obvious problem with this solution is that we did not explicitly return anything to the function caller in the error situation. Even though None is returned (when a function does not return any value explicitly, i.e., completing execution without encountering a return object statement), we give little or no hint that anything wrong took place. The very least we should do is to explicitly return None so that our function returns a value in both cases and makes our code somewhat easier to understand:

image

Bear in mind that with our change above, nothing about our code changed except that we used one more local variable. In designing a well-written application programmer interface (API), you may have kept the return value more flexible. Perhaps you documented that if a proper argument was passed to safe_float(), then indeed, a floating point number would be returned, but in the case of an error, you chose to return a string indicating the problem with the input value. We modify our code one more time to reflect this change:

image

The only thing we changed in the example was to return an error string as opposed to just None. We should take our function out for a test drive to see how well it works so far:

image

We made a good start—now we can detect invalid string input, but we are still vulnerable to invalid objects being passed in:

image

We will address this final shortcoming momentarily, but before we further modify our example, we would like to highlight the flexibility of the try-except syntax, especially the except statement, which comes in a few more flavors.

10.3.3 try Statement with Multiple excepts

Earlier in this chapter, we introduced the following general syntax for except:

image

The except statement in such formats specifically detects exceptions named Exception. You can chain multiple except statements together to handle different types of exceptions with the same try:

image

This same try clause is attempted, and if there is no error, execution continues, passing all the except clauses. However, if an exception does occur, the interpreter will look through your list of handlers attempting to match the exception with one of your handlers (except clauses). If one is found, execution proceeds to that except suite.

Our safe_float() function has some brains now to detect specific exceptions. Even smarter code would handle each appropriately. To do that, we have to have separate except statements, one for each exception type. That is no problem as Python allows except statements can be chained together. We will now create separate messages for each error type, providing even more detail to the user as to the cause of his or her problem:

image

Running the code above with erroneous input, we get the following:

image

10.3.4 except Statement with Multiple Exceptions

We can also use the same except clause to handle multiple exceptions. except statements that process more than one exception require that the set of exceptions be contained in a tuple:

image

The above syntax example illustrates how two exceptions can be handled by the same code. In general, any number of exceptions can follow an except statement as long as they are all properly enclosed in a tuple:

image

If for some reason, perhaps due to memory constraints or dictated as part of the design that all exceptions for our safe_float() function must be handled by the same code, we can now accommodate that requirement:

image

Now there is only the single error string returned on erroneous input:

image

10.3.5 Catching All Exceptions

Using the code we saw in the previous section, we are able to catch any number of specific exceptions and handle them. What about cases where we want to catch all exceptions? The short answer is yes, we can definitely do it. The code for doing it was significantly improved in 1.5 when exceptions became classes. Because of this, we now have an exception hierarchy to follow.

If we go all the way up the exception tree, we find Exception at the top, so our code will look like this:

image

Less preferred is the bare except clause:

image

This syntax is not as “Pythonic” as the other. Although this code catches the most exceptions, it does not promote good Python coding style. One of the chief reasons is that it does not take into account the potential root causes of problems that may generate exceptions. Rather than investigating and discovering what types of errors may occur and how they may be prevented from happening, we have a catch-all that may not do the right thing.

We are not naming any specific exceptions to catch—it does not give us any information about the possible errors that could happen in our try block. Another thing is that by catching all errors, you may be silently dropping important errors that really should be sent to the caller to properly take care of them. Finally, we do not have the opportunity to save the reason for the exception. Yes, you can get it through sys.exc_ info(), but then you would have to import sys and execute that function—both of which can be avoided, especially if all we wanted was the instance telling us why the exception occurred. It is a distinct possibility that the bare exception clause will be deprecated in a future release of Python. (See also Core Style note).

One aspect of catching all exceptions that you need to be aware of is that there are several exceptions that are not due to an error condition. These two exceptions are SystemExit and KeyboardInterrupt. SystemExit is for when the current Python application wants to quit, and KeyboardInterrupt is when a user presses CTRL-C (^C) to terminate Python. These will be caught by both code snippets above when we really want to pass them upward. A typical workaround code pattern will look like this:

image

A few things regarding exceptions did change in Python 2.5. Exceptions were moved to new-style classes, a new “mother of all exception” classes named BaseException was installed, and the exception hierarchy was switched around (very slightly) to get rid of that idiom of having to create two handlers. Both KeyboardInterrupt and SystemExit have been pulled out from being children of Exception to being its peers:

image

image

You can find the entire exception hierarchy (before and after these changes) in Table 10.2.

The end result is that now you do not have to write the extra handler for those two exceptions if you have a handler for just Exception. This code will suffice:

image

If you really want to catch all errors, you can still do that too, but use BaseException instead:

image

And of course, there is the less preferred bare except.

Core Style: Do not handle and ignore all errors

image

The try-except statement has been included in Python to provide a powerful mechanism for programmers to track down potential errors and perhaps to provide logic within the code to handle situations where it may not otherwise be possible, for example, in C. The main idea is to minimize the number of errors and still maintain program correctness. As with all tools, they must be used properly.

One incorrect use of try-except is to serve as a giant bandage over large pieces of code. By that we mean putting large blocks, if not your entire source code, within a try and/or have a large generic except to “filter” any fatal errors by ignoring them:

image

Obviously, errors cannot be avoided, and the job of try-except is to provide a mechanism whereby an acceptable problem can be remedied or properly dealt with, and not be used as a filter. The construct above will hide many errors, but this type of usage promotes a poor engineering practice that we certainly cannot endorse.

Bottom line: Avoid using try-except around a large block of code with a pass just to hide errors. Instead, either handle specific exceptions and ignore them (pass), or handle all errors and take a specific action. Do not do both (handle all errors, ignore all errors).

10.3.6 “Exceptional Arguments”

No, the title of this section has nothing to do with having a major fight. Instead, we are referring to the fact that an exception may have an argument or reason passed along to the exception handler when they are raised. When an exception is raised, parameters are generally provided as an additional aid for the exception handler. Although reasons for exceptions are optional, the standard built-in exceptions do provide at least one argument, an error string indicating the cause of the exception.

Exception parameters can be ignored in the handler, but the Python provides syntax for saving this value. We have already seen it in the syntax above: to access any provided exception reason, you must reserve a variable to hold the argument. This argument is given on the except header line and follows the exception type you are handling. The different syntaxes for the except statement can be extended to the following:

image

reason is a class instance containing diagnostic information from the code raising the exception. The exception arguments themselves go into a tuple that is stored as an attribute of the class instance, an instance of the exception class from which it was instantiated. In the first alternate syntax above, reason is an instance of the Exception class.

For most standard built-in exceptions, that is, exceptions derived from StandardError, the tuple consists of a single string indicating the cause of the error. The actual exception name serves as a satisfactory clue, but the error string enhances the meaning even more. Operating system or other environment type errors, i.e., IOError, will also include an operating system error number that precedes the error string in the tuple.

Whether a reason contains just a string or a combination of an error number and a string, calling str(reason) should present a human-readable cause of an error. However, do not lose sight that reason is really a class instance—you are only getting the error information via that class’s special method __str__(). We have a complete treatment of special methods as we explore object-oriented programming in Chapter 13.

The only caveat is that not all exceptions raised in third-party or otherwise external modules adhere to this standard protocol of error string or error number and error string. We recommend you follow such a standard when raising your own exceptions (see Core Style note).

Core Style: Follow exception argument protocol

image

When you raise built-in exceptions in your own code, try to follow the protocol established by the existing Python code as far as the error information that is part of the tuple passed as the exception argument. In other words, if you raise a ValueError, provide the same argument information as when the interpreter raises a ValueError exception, and so on. This helps keep the code consistent and will prevent other applications that use your module from breaking.

The example below is when an invalid object is passed to the float() built-in function, resulting in a TypeError exception:

image

The first thing we did was cause an exception to be raised from within the try statement. Then we passed cleanly through by ignoring but saving the error information. Calling the type() built-in function, we were able to confirm that our exception was indeed an instance of the TypeError exception class. Finally, we displayed the error by calling print with our diagnostic exception argument.

To obtain more information regarding the exception, we can use the special __class__ instance attribute, which identifies which class an instance was instantiated from. Class objects also have attributes, such as a documentation string and a string name that further illuminate the error type:

image

As we will discover in Chapter 13— the special instance attribute __class__ exists for all class instances, and the __doc__ class attribute is available for all classes that define their documentation strings.

We will now update our safe_float() one more time to include the exception argument, which is passed from the interpreter from within float() when exceptions are generated. In our last modification to safe_float(), we merged both the handlers for the ValueError and TypeError exceptions into one because we had to satisfy some requirement. The problem, if any, with this solution is that no clue is given as to which exception was raised or what caused the error. The only thing returned is an error string that indicated some form of invalid argument. Now that we have the exception argument, this no longer has to be the case.

Because each exception will generate its own exception argument, if we chose to return this string rather than a generic one we made up, it would provide a better clue as to the source of the problem. In the following code snippet, we replace our single error string with the string representation of the exception argument.

image

Upon running our new code, we obtain the following (different) messages when providing improper input to safe_float(), even if both exceptions are managed by the same handler:

image

10.3.7 Using Our Wrapped Function in an Application

We will now feature safe_float() in a mini application that takes a credit card transaction data file (carddata.txt) and reads in all transactions, including explanatory strings. Here are the contents of our example carddata.txt file:

image

Our program, cardrun.py, is given in Example 10.1.

Example 10.1. Credit Card Transactions (cardrun.py)

We use safe_float() to process a set of credit card transactions given in a file and read in as strings. A log file tracks the processing.

image

Line-by-Line Explanation

Lines 3–9

This chunk of code contains the body of our safe_float() function.

Lines 11–34

The core part of our application performs three major tasks: (1) read the credit card data file, (2) process the input, and (3) display the result. Lines 14-22 perform the extraction of data from the file. You will notice that there is a try-except statement surrounding the file open.

A log file of the processing is also kept. In our example, we are assuming the log file can be opened for write without any problems. You will find that our progress is kept by the log. If the credit card data file cannot be accessed, we will assume there are no transactions for the month (lines 16-19).

The data are then read into the txns (transactions) list where it is iterated over in lines 26-32. After every call to safe_float(), we check the result type using the isinstance() built-in function. In our example, we check to see if safe_float() returns a string or float. Any string indicates an error situation with a string that could not be converted to a number, while all other values are floats that can be added to the running subtotal. The final new balance is then displayed as the final line of the main() function.

Lines 36–37

These lines represent the general “start only if not imported” functionality.

Upon running our program, we get the following output:

$ cardrun.py
$58.94 (new balance)

Taking a peek at the resulting log file (cardlog.txt), we see that it contains the following log entries after cardrun.py processed the transactions found in carddata.txt:

image

10.3.8 else Clause

We have seen the else statement with other Python constructs such as conditionals and loops. With respect to try-except statements, its functionality is not that much different from anything else you have seen: The else clause executes if no exceptions were detected in the preceding try suite.

All code within the try suite must have completed successfully (i.e., concluded with no exceptions raised) before any code in the else suite begins execution. Here is a short example in Python pseudocode:

image

In the preceding example, we import an external module and test it for errors. A log file is used to determine whether there were defects in the third-party module code. Depending on whether an exception occurred during execution of the external function, we write differing messages to the log.

10.3.9 finally Clause

A finally clause is one where its suite or block of code is executed regardless of whether an exception occurred or whether it was caught (or not). You may use a finally clause with try by itself or with try-except (with or without an else clause). The standalone try-finally is covered in the next section, so we will just focus on the latter here.

image

Starting in Python 2.5, you can use the finally clause (again) with try-except or try-except-else. We say “again” because believe it or not, it is not a new feature. This was a feature available in Python back in the early days but was removed in Python 0.9.6 (April 1992). At the time, it helped simplify the bytecode generation process and was easier to explain, and van Rossum believed that a unified try-except (-else)-finally would not be very popular anyway. How things change well over a decade later!

Here is what the syntax would look like with try-except-else-finally:

image

The equivalent in Python 0.9.6 through 2.4.x. is the longer:

image

Of course, in either case, you can have more than one except clause, however the syntax requires at least one except clause and both the else and finally clauses are optional. A,B,C, and D are suites (code blocks). The suites will execute in that order as necessary. (Note the only flows possible are A-C-D [normal] and A-B-D [exception].) The finally block will be executed whether exceptions occur in A,B, and/or C. Code written with the older idiom will continue to run, so there are no backward-compatibility problems.

10.3.10 try-finally Statement

An alternative is to use finally alone with try. The try-finally statement differs from its try-except brethren in that it is not used to handle exceptions. Instead it is used to maintain consistent behavior regardless of whether or not exceptions occur. We know that the finally suite executes regardless of an exception being triggered within the try suite.

image

When an exception does occur within the try suite, execution jumps immediately to the finally suite. When all the code in the finally suite completes, the exception is reraised for handling at the next higher layer. Thus it is common to see a try-finally nested as part of a try-except suite.

One place where we can add a try-finally statement is by improving our code in cardrun.py so that we catch any problems that may arise from reading the data from the carddata.txt file. In the current code in Example 10.1, we do not detect errors during the read phase (using readlines()):

image

It is possible for readlines() to fail for any number of reasons, one of which is if carddata.txt was a file on the network (or a floppy) that became inaccessible. Regardless, we should improve this piece of code so that the entire input of data is enclosed in the try clause:

image

All we did was to move the readlines() and close() method calls to the try suite. Although our code is more robust now, there is still room for improvement. Notice what happens if there was an error of some sort. If the open succeeds, but for some reason the readlines() call does not, the exception will continue with the except clause. No attempt is made to close the file. Wouldn’t it be nice if we closed the file regardless of whether an error occurred or not? We can use try-finally:

image

This code snippet will attempt to open the file and read in the data. If an error occurs during this step, it is logged, and then the file is properly closed. If no errors occur, the file is still closed. (The same functionality can be achieved using the unified try-except-finally statement above.) An alternative implementation involves switching the try-except and try-finally clauses:

image

The code works virtually the same with some differences. The most obvious one is that the closing of the file happens before the exception handler writes out the error to the log. This is because finally automatically reraises the exception.

One argument for doing it this way is that if an exception happens within the finally block, you are able to create another handler at the same outer level as the one we have, so in essence, be able to handle errors in both the original try block as well as the finally block. The only thing you lose when you do this is that if the finally block does raise an exception, you have lost context of the original exception unless you have saved it somewhere.

An argument against having the finally inside the except is that in many cases, the exception handler needs to perform some cleanup tasks as well, and if you release those resources with a finally block that comes before the exception handler, you have lost the ability to do so. In other words, the finally block is not as “final” as one would think.

One final note: If the code in the finally suite raises another exception, or is aborted due to a return, break, or continue statement, the original exception is lost and cannot be reraised.

10.3.11 try-except-else-finally: aka the Kitchen Sink

We can combine all the varying syntaxes that we have seen so far in this chapter to highlight all the different ways you can handle exceptions:

image

Recall from above that using a finally clause combined with try-except or try-except-else is “new” as of Python 2.5. The most important thing to take away from this section regarding the syntax is that you must have at least one except clause; both the else and finally clauses are optional.

10.4 Context Management

10.4.1 with Statement

The unification of try-except and try-finally as described above makes programs more “Pythonic,” meaning, among many other characteristics, simpler to write and easier to read. Python already does a great job at hiding things under the covers so all you have to do is worry about how to solve the problem you have. (Can you imagine porting a complex Python application into C++ or Java?!?)

image

Another example of hiding lower layers of abstraction is the with statement, made official as of Python 2.6. (It was introduced in 2.5 as a preview and to serve warnings for those applications using with as an identifier that it will become a keyword in 2.6. To use this feature in 2.5, you must import it with from __future__ import with_statement.)

Like try-except-finally, the with statement, has a purpose of simplifying code that features the common idiom of using the try-except and try-finally pairs in tandem. The specific use that the with statement targets is when try-except and try-finally are used together in order to achieve the sole allocation of a shared resource for execution, then releasing it once the job is done. Examples include files (data, logs, database, etc.), threading resources and synchronization primitives, database connections, etc.

However, instead of just shortening the code and making it easier to use like try-except-finally, the with statement’s goal is to remove the try, except, and finally keywords and the allocation and release code from the picture altogether. The basic syntax of the with statement looks like this:

image

It looks quite simple, but making it work requires some work under the covers. The reason is it not as simple as it looks is because you cannot use the with statement merely with any expression in Python. It only works with objects that support what is called the context management protocol. This simply means that only objects that are built with “context management” can be used with a with statement. We will describe what that means soon.

Now, like any new video game hardware, when this feature was released, some folks out there took the time to develop new games for it so that you can play when you open the box. Similarly, there were already some Python objects that support the protocol. Here is a short list of the first set:

file

decimal.Context

thread.LockType

threading.Lock

threading.RLock

threading.Condition

threading.Semaphore

threading.BoundedSemaphore

Since files are first on the list and the simplest example, here is a code snippet of what it looks like to use a with statement:

image

What this code snippet will do is... well, this is Python, so you can probably already guess. It will do some preliminary work, such as attempt to open the file, and if all goes well, assign the file object to f. Then it iterates over each line in the file and does whatever processing you need to do. Once the file has been exhausted, it is closed. If an exception occurs either at the beginning, middle, or end of the block, then some cleanup code must be done, but the file will still be closed automatically.

Now, because a lot of the details have been pushed down and away from you, there are really two levels of processing that need to occur: First, the stuff at the user level—as in, the things you need to take care of as the user of the object—and second, at the object level. Since this object supports the context management protocol, it has to do some “context management.“

10.4.2 *Context Management Protocol

Unless you will be designing objects for users of the with statement, i.e., programmers who will be using your objects to design their applications with, most Python programmers are going to be just users of the with statement and can skip this optional section.

We are not going into a full and deep discussion about context management here, but we will explain the types of objects and the functionality that are necessary to be protocol-compliant and thus be eligible to be used with the with statement.

Previously, we described a little of how the protocol works in our example with the file object. Let us elaborate some more here.

Context Expression (context_expr), Context Manager

When the with statement is executed, the context expression is evaluated to obtain what is called a context manager. The job of the context manager is to provide a context object. It does this by invoking its required __context__() special method. The return value of this method is the context object that will be used for this particular execution of the with_suite. One side note is that a context object itself can be its own manager, so context_expr can really be either a real context manager or a context object serving as its own manager. In the latter case, the context object also has a __context__() method, which returns self, as expected.

Context Object, with_suite

Once we have a context object, its __enter__() special method is invoked. This does all the preliminary stuff before the with_suite executes. You will notice in the syntax above that there is an optional as var piece following context_expr on the with statement line. If var is provided, it is assigned the return value of __enter__(). If not, the return value is thrown away. So for our file object example, its context object’s __enter__() returns the file object so it can be assigned to f.

Now the with_suite executes. When execution of with_suite terminates, whether “naturally” or via exception, the context object’s __exit__() special method is called. __exit__()takes three arguments. If with_suite terminates normally, all three parameters passed in are None. If an exception occurred, then the three arguments are the same three values returned when calling the sys.exc_info() function (see section 10.12): type (exception class), value (this exception’s instance), and traceback, the corresponding traceback object.

It is up to you to decide how you want to handle the exception here in __exit__(). The usual thing to do after you are done is not to return anything from __exit__() or return None or some other Boolean False object. This will cause the exception to be reraised back to your user for handling. If you want to explicitly silence the exception, then return any object that has a Boolean True value. If an exception did not occur or you returned True after handling an exception, the program will continue on the next statement after the with clause.

Since context management makes the most sense for shared resources, you can imagine that the __enter__() and __exit__() methods will primarily be used for doing the lower-level work required to allocate and release resources, i.e., database connections, lock allocation, semaphore decrement, state management, opening/closing of files, exception handling, etc.

To help you with writing context managers for objects, there is the contextlib module, which contains useful functions/decorators with which you can apply over your functions or objects and not have to worry about implementing a class or separate __context__(), __enter__(), __exit__() special methods.

For more information or more examples of context management, check out the official Python documentation on the with statement and contextlib module, class special methods (related to with and contexts), PEP 343, and the “What’s New in Python 2.6” document.

10.5 *Exceptions as Strings

Prior to Python 1.5, standard exceptions were implemented as strings. However, this became limiting in that it did not allow for exceptions to have relationships to each other. With the advent of exception classes, this is no longer the case. As of 1.5, all standard exceptions are now classes. It is still possible for programmers to generate their own exceptions as strings, but we recommend using exception classes from now on.

For backward compatibility, it is possible to revert to string-based exceptions. Starting the Python interpreter with the command-line option -X will provide you with the standard exceptions as strings. This feature will be obsolete beginning with Python 1.6.

Python 2.5 begins the process of deprecating string exceptions from Python forever. In 2.5, raise of string exceptions generates a warning. In 2.6, the catching of string exceptions results in a warning. Since they are rarely used and are being deprecated, we will no longer consider string exceptions within the scope of this book and have removed it. (You may find the original text in prior editions of this book.) The only point of relevance and the final thought is a caution: You may use an external or third-party module, which may still have string exceptions. String exceptions are a bad idea anyway. One reader vividly recalls seeing Linux RPM exceptions with spelling errors in the exception text.

image

10.6 Raising Exceptions

The interpreter was responsible for raising all of the exceptions we have seen so far. These exist as a result of encountering an error during execution. A programmer writing an API may also wish to throw an exception on erroneous input, for example, so Python provides a mechanism for the programmer to explicitly generate an exception: the raise statement.

10.6.1 raise Statement

Syntax and Common Usage

The raise statement is quite flexible with the arguments it supports, translating to a large number of different formats supported syntactically. The general syntax for raise is:

raise [SomeException [, args [, traceback]]]

The first argument, SomeException, is the name of the exception to raise. If present, it must either be a string, class, or instance (more below). SomeException must be given if any of the other arguments (args or traceback) are present. A list of all Python standard exceptions is given in Table 10.2.

The second expression contains optional args (aka parameters, values) for the exception. This value is either a single object or a tuple of objects. When exceptions are detected, the exception arguments are always returned as a tuple. If args is a tuple, then that tuple represents the same set of exception arguments that are given to the handler. If args is a single object, then the tuple will consist solely of this one object (i.e., a tuple with one element). In most cases, the single argument consists of a string indicating the cause of the error. When a tuple is given, it usually equates to an error string, an error number, and perhaps an error location, such as a file, etc.

The final argument, traceback, is also optional (and rarely used in practice), and, if present, is the traceback object used for the exception—normally a traceback object is newly created when an exception is raised. This third argument is useful if you want to reraise an exception (perhaps to point to the previous location from the current). Arguments that are absent are represented by the value None.

The most common syntax used is when SomeException is a class. No additional parameters are ever required, but in this case, if they are given, they can be a single object argument, a tuple of arguments, or an exception class instance. If the argument is an instance, then it can be an instance of the given class or a derived class (subclassed from a pre-existing exception class). No additional arguments (i.e., exception arguments) are permitted if the argument is an instance.

More Exotic/Less Common Usage

What happens if the argument is an instance? No problems arise if instance is an instance of the given exception class. However, if instance is not an instance of the class or an instance of a subclass of the class, then a new instance of the exception class will be created with exception arguments copied from the given instance. If instance is an instance of a subclass of the exception class, then the new exception will be instantiated from the subclass, not the original exception class.

If the additional parameter to the raise statement used with an exception class is not an instance—instead, it is a singleton or tuple—then the class is instantiated and args is used as the argument list to the exception. If the second parameter is not present or None, then the argument list is empty.

If SomeException is an instance, then we do not need to instantiate anything. In this case, additional parameters must not be given or must be None. The exception type is the class that instance belongs to; in other words, this is equivalent to raising the class with this instance, i.e., raise instance.__class__, instance.

Use of string exceptions is deprecated in favor of exception classes, but if SomeException is a string, then it raises the exception identified by string, with any optional parameters (args) as arguments.

Finally, the raise statement by itself without any parameters is a new construct, introduced in Python 1.5, and causes the last exception raised in the current code block to be reraised. If no exception was previously raised, a TypeError exception will occur, because there was no previous exception to reraise.

Due to the many different valid syntax formats for raise (i.e., SomeException can be either a class, instance, or a string), we provide Table 10.1 to illuminate all the different ways which raise can be used.

Table 10.1. Using the raise Statement

image image

10.7 Assertions

Assertions are diagnostic predicates that must evaluate to Boolean True; otherwise, an exception is raised to indicate that the expression is false. These work similarly to the assert macros, which are part of the C language preprocessor, but in Python these are runtime constructs (as opposed to precompile directives).

If you are new to the concept of assertions, no problem. The easiest way to think of an assertion is to liken it to a raise-if statement (or to be more accurate, a raise-if-not statement). An expression is tested, and if the result comes up false, an exception is raised.

Assertions are carried out by the assert statement, introduced back in version 1.5.

10.7.1 assert Statement

The assert statement evaluates a Python expression, taking no action if the assertion succeeds (similar to a pass statement), but otherwise raising an AssertionError exception. The syntax for assert is:

assert expression [, arguments]

Here are some examples of the use of the assert statement:

image

AssertionError exceptions can be caught and handled like any other exception using the try-except statement, but if not handled, they will terminate the program and produce a traceback similar to the following:

image

As with the raise statement we investigated in the previous section, we can provide an exception argument to our assert command:

image

Here is how we would use a try-except statement to catch an AssertionError exception:

image

Executing the above code from the command line would result in the following output:

AssertionError: One does not equal zero silly!

To give you a better idea of how assert works, imagine how the assert statement may be implemented in Python if written as a function. It would probably look something like this:

image

The first if statement confirms the appropriate syntax for the assert, meaning that expr should be an expression. We compare the type of expr to a real expression to verify. The second part of the function evaluates the expression and raises AssertionError, if necessary. The built-in variable __debug__ is 1 under normal circumstances, 0 when optimization is requested (command-line option -O).

10.8 Standard Exceptions

Table 10.2 lists all of Python’s current set of standard exceptions. All exceptions are loaded into the interpreter as built-ins so they are ready before your script starts or by the time you receive the interpreter prompt, if running interactively.

Table 10.2. Python Built-In Exceptions

image

image image

image

All standard/built-in exceptions are derived from the root class BaseException. There are currently three immediate subclasses of BaseException: SystemExit, KeyboardInterrupt, and Exception. All other built-in exceptions are subclasses of Exceptions. Every level of indentation of an exception listed in Table 10.2 indicates one level of exception class derivation.

image

As of Python 2.5, all exceptions are new-style classes and are ultimately subclassed from BaseException. At this release, SystemExit and KeyboardInterrupt were taken out of the hierarchy for Exception and moved up to being under BaseException. This is to allow statements like except Exception to catch all errors and not program exit conditions.

From Python 1.5 through Python 2.4.x, exceptions were classic classes, and prior to that, they were strings. String-based exceptions are no longer acceptable constructs and are officially deprecated beginning with 2.5, where you will not be able to raise string exceptions. In 2.6, you cannot catch them.

There is also a requirement that all new exceptions be ultimately subclassed from BaseException so that all exceptions will have a common interface. This will transition will begin with Python 2.7 and continue through the remainder of the 2.x releases.

10.9 *Creating Exceptions

Although the set of standard exceptions is fairly wide-ranging, it may be advantageous to create your own exceptions. One situation is where you would like additional information from what a standard or module-specific exception provides. We will present two examples, both related to IOError.

IOError is a generic exception used for input/output problems, which may arise from invalid file access or other forms of communication. Suppose we wanted to be more specific in terms of identifying the source of the problem. For example, for file errors, we want to have a FileError exception that behaves like IOError, but with a name that has more meaning when performing file operations.

Another exception we will look at is related to network programming with sockets. The exception generated by the socket module is called socket.error and is not a built-in exception. It is subclassed from the generic Exception exception. However, the exception arguments from socket.error closely resemble those of IOError exceptions, so we are going to define a new exception called NetworkError, which subclasses from IOError but contains at least the information provided by socket.error.

Like classes and object-oriented programming, we have not formally covered network programming at this stage, but skip ahead to Chapter 16 if you need to.

We now present a module called myexc.py with our newly customized exceptions FileError and NetworkError. The code is in Example 10.2.

Example 10.2. Creating Exceptions (myexc.py)

This module defines two new exceptions, FileError and NetworkError, as well as reimplements more diagnostic versions of open() [myopen()] and socket.connect() [myconnect()]. Also included is a test function [test()] that is run if this module is executed directly.

image

image

image

Lines 1–3

The Unix startup script and importation of the socket, os, errno, types, and tempfile modules help us start this module.

Lines 5–9

Believe it or not, these five lines make up our new exceptions. Not just one, but both of them. Unless new functionality is going to be introduced, creating a new exception is just a matter of subclassing from an already existing exception. In our case, that would be IOError. EnvironmentError, from which IOError is derived, would also work, but we wanted to convey that our exceptions were definitely I/O-related.

We chose IOError because it provides two arguments, an error number and an error string. File-related [uses open()] IOError exceptions even support a third argument that is not part of the main set of exception arguments, and that would be the filename. Special handling is done for this third argument, which lives outside the main tuple pair and has the name filename.

Lines 11–21

The entire purpose of the updArgs() function is to “update” the exception arguments. What we mean here is that the original exception is going to provide us a set of arguments. We want to take these arguments and make them part of our new exception, perhaps embellishing or adding a third argument (which is not added if nothing is given—None is a default argument, which we will study in the next chapter). Our goal is to provide the more informative details to the user so that if and when errors occur, the problems can be tracked down as quickly as possible.

Lines 23–53

The fileArgs() function is used only by myopen() (see below). In particular, we are seeking error EACCES, which represents “permission denied.” We pass all other IOError exceptions along without modification (lines 54-55). If you are curious about ENXIO, EACCES, and other system error numbers, you can hunt them down by starting at file /usr/include/sys/errno.h on a Unix system, or C:MsdevincludeErrno.h if you are using Visual C++ on Windows.

In line 27, we are also checking to make sure that the machine we are using supports the os.access() function, which helps you check what kind of file permissions you have for any particular file. We do not proceed unless we receive both a permission error as well as the ability to check what kind of permissions we have. If all checks out, we set up a dictionary to help us build a string indicating the permissions we have on our file.

The Unix file system uses explicit file permissions for the user, group (more than one user can belong to a group), and other (any user other than the owner or someone in the same group as the owner) in read, write, and execute (`r', `w', `x') order. Windows supports some of these permissions.

Now it is time to build the permission string. If the file has a permission, its corresponding letter shows up in the string, otherwise a dash ( - ) appears. For example, a string of “rw-” means that you have read and write access to it. If the string reads “r-x”, you have only read and execute access; “---” means no permission at all.

After the permission string has been constructed, we create a temporary argument list. We then alter the error string to contain the permission string, something that standard IOError exception does not provide. “Permission denied” sometimes seems silly if the system does not tell you what permissions you have to correct the problem. The reason, of course, is security. When intruders do not have permission to access something, the last thing you want them to see is what the file permissions are, hence the dilemma. However, our example here is merely an exercise, so we allow for the temporary “breach of security.” The point is to verify whether or not the os.chmod() functions call affected file permissions the way they are supposed to.

The final thing we do is to add the filename to our argument list and return the set of arguments as a tuple.

Lines 55–65

Our new myconnect() function simply wraps the standard socket method connect() to provide an IOError-type exception if the network connection fails. Unlike the general socket.error exception, we also provide the hostname and port number as an added value to the programmer.

For those new to network programming, a hostname and port number pair are analogous to an area code and telephone number when you are trying to contact someone. In this case, we are trying to contact a program running on the remote host, presumably a server of some sort; therefore, we require the host’s name and the port number that the server is listening on.

When a failure occurs, the error number and error string are quite helpful, but it would be even more helpful to have the exact host-port combination as well, since this pair may be dynamically generated or retrieved from some database or name service. That is the value-add we are bestowing on our version of connect(). Another issue arises when a host cannot be found. There is no direct error number given to us by the socket.error exception, so to make it conform to the IOError protocol of providing an error number-error string pair, we find the closest error number that matches. We choose ENXIO.

Lines 67–73

Like its sibling myconnect(), myopen() also wraps around an existing piece of code. Here, we have the open() function. Our handler catches only IOError exceptions. All others will pass through and on up to the next level (when no handler is found for them). Once an IOError is caught, we raise our own error and customized arguments as returned from fileArgs().

Lines 75–95

We shall perform the file testing first, here using the testfile() function. In order to begin, we need to create a test file that we can manipulate by changing its permissions to generate permission errors. The tempfile module contains code to create temporary file names or temporary files themselves. We just need the name for now and use our new myopen() function to create an empty file. Note that if an error occurred here, there would be no handler, and our program would terminate fatally—the test program should not continue if we cannot even create a test file.

Our test uses four different permission configurations. A zero means no permissions at all, 0100 means execute-only, 0400 indicates read-only, and 0500 means read- and execute-only (0400 + 0100). In all cases, we will attempt to open a file with an invalid mode. The os.chmod() function is responsible for updating a file’s permission modes. (Note: These permissions all have a leading zero in front, indicating that they are octal [base 8] numbers.)

If an error occurs, we want to display diagnostic information similar to the way the Python interpreter performs the same task when uncaught exceptions occur, and that is giving the exception name followed by its arguments. The __class__ special variable represents the class object from which an instance was created. Rather than displaying the entire class name here (myexc.FileError), we use the class object’s __name__ variable to just display the class name (FileError), which is also what you see from the interpreter in an unhandled error situation. Then the arguments that we arduously put together in our wrapper functions follow.

If the file opened successfully, that means the permissions were ignored for some reason. We indicate this with a diagnostic message and close the file. Once all tests have been completed, we enable all permissions for the file and remove it with the os.unlink() function. (os.remove() is equivalent to os.unlink().)

Lines 97–106

The next section of code (testnet()) tests our NetworkError exception. A socket is a communication endpoint with which to establish contact with another host. We create such an object, then use it in an attempt to connect to a host with no server to accept our connect request and a host not on our network.

Lines 108–110

We want to execute our test*() functions only when invoking this script directly, and that is what the code here does. Most of the scripts given in this text utilize the same format.

Running this script on a Unix-flavored box, we get the following output:

image

The results are slightly different on a Win32 machine:

image

You will notice that Windows does not support read permissions on files, which is the reason why the first two file open attempts succeeded. Your mileage may vary (YMMV) on your own machine and operating system.

10.10 Why Exceptions (Now)?

There is no doubt that errors will be around as long as software is around. The difference in today’s fast-paced computing world is that our execution environments have changed, and so has our need to adapt error-handling to accurately reflect the operating context of the software that we develop. Modern-day applications generally run as self-contained graphical user interfaces (GUIs) or in a client/server architecture such as the Web.

The ability to handle errors at the application level has become even more important recently in that users are no longer the only ones directly running applications. As the Internet and online electronic commerce become more pervasive, Web servers will be the primary users of application software. This means that applications cannot just fail or crash outright anymore, because if they do, system errors translate to browser errors, and these in turn lead to frustrated users. Losing eyeballs means losing advertising revenue and potentially significant amounts of irrecoverable business.

If errors do occur, they are generally attributed to some invalid user input. The execution environment must be robust enough to handle the application-level error and be able to produce a user-level error message. This must translate to a “non-error” as far as the Web server is concerned because the application must complete successfully, even if all it does is return an error message to present to the user as a valid Hypertext Markup Language (HTML) Web page displaying the error.

If you are not familiar with what I am talking about, does a plain Web browser screen with the big black words saying, “Internal Server Error” sound familiar? How about a fatal error that brings up a pop-up that declares “Document contains no data”? As a user, do either of these phrases mean anything to you? No, of course not (unless you are an Internet software engineer), and to the average user, they are an endless source of confusion and frustration. These errors are a result of a failure in the execution of an application. The application either returns invalid Hypertext Transfer Protocol (HTTP) data or terminates fatally, resulting in the Web server throwing its hands up into the air, saying, “I give up!“

This type of faulty execution should not be allowed, if at all possible. As systems become more complex and involve more apprentice users, additional care should be taken to ensure a smooth user application experience. Even in the face of an error situation, an application should terminate successfully, as to not affect its execution environment in a catastrophic way. Python’s exception handling promotes mature and correct programming.

10.11 Why Exceptions at All?

If the above section was not motivation enough, imagine what Python programming might be like without program-level exception handling. The first thing that comes to mind is the loss of control client programmers have over their code. For example, if you created an interactive application that allocates and utilizes a large number of resources, if a user hit ^C or other keyboard interrupt, the application would not have the opportunity to perform cleanup, resulting in perhaps loss of data or data corruption. There is also no mechanism to take alternative action such as prompting the users to confirm whether they really want to quit or if they hit the Control key accidentally.

Another drawback would be that functions would have to be rewritten to return a “special” value in the face of an error situation, for example, None. The engineer would be responsible for checking each and every return value from a function call. This may be cumbersome because you may have to check return values, which may not be of the same type as the object you are expecting if no errors occurred. And what if your function wants to return None as a valid data value? Then you would have to come up with another return value, perhaps a negative number. We probably do not need to remind you that negative numbers may be valid in a Python context, such as an index into a sequence. As a programmer of application programmer interfaces (APIs), you would then have to document every single return error your users may encounter based on the input received. Also, it is difficult (and tedious) to propagate errors (and reasons) of multiple layers of code.

There is no simple propagation like the way exceptions do it. Because error data needs to be transmitted upwards in the call hierarchy, it is possible to misinterpret the errors along the way. A totally unrelated error may be stated as the cause when in fact it had nothing to do with the original problem to begin with. We lose the bottling-up and safekeeping of the original error that exceptions provide as they are passed from layer to layer, not to mention completely losing track of the data we were originally concerned about! Exceptions simplify not only the code, but the entire error management scheme, which should not play such a significant role in application development. And with Python’s exception handling capabilities, it does not have to.

10.12 Exceptions and the sys Module

An alternative way of obtaining exception information is by accessing the exc_info() function in the sys module. This function provides a 3-tuple of information, more than what we can achieve by simply using only the exception argument. Let us see what we get using sys.exc_info():

image

What we get from sys.exc_info() in a tuple are:

exc_type: exception class object

exc_value: (this) exception class instance object

exc_traceback: traceback object

The first two items we are familiar with: the actual exception class and this particular exception’s instance (which is the same as the exception argument which we discussed in the previous section). The third item, a traceback object, is new. This object provides the execution context of where the exception occurred. It contains information such as the execution frame of the code that was running and the line number where the exception occurred.

In older versions of Python, these three values were available in the sys module as sys.exc_type, sys.exc_value, and sys.exc_traceback. Unfortunately, these three are global variables and not thread-safe. We recommend using sys.exc_info() instead. All three will be phased out and eventually removed in a future version of Python.

10.13 Related Modules

Table 10.3 lists some of the modules related to this chapter.

Table 10.3. Exception-Related Standard Library Modules

image

10.14 Exercises

10-1. Raising Exceptions. Which of the following can raise exceptions during program execution? Note that this question does not ask what may cause exceptions.

(a) The user (of your program)

(b) The interpreter

(c) The program(er)

(d) All of the above

(e) Only (b) and (c)

(f) Only (a) and (c)

10-2. Raising Exceptions. Referring to the list in the problem above, which could raise exceptions while running within the interactive interpreter?

10-3. Keywords. Name the keyword(s) which is (are) used to raise exceptions.

10-4. Keywords. What is the difference between try-except and try-finally?

10-5. Exceptions. Name the exception that would result from executing the following pieces of Python code from within the interactive interpreter (refer back to Table 10.2 for a list of all built-in exceptions):

(a) >>> if 3 < 4 then: print '3 IS less than 4!'

(b) >>> aList = ['Hello', 'World!', 'Anyone', 'Home?']
  >>> print 'the last string in aList is:', aList
  [len(aList)]

(c) >>> x

(d) >>> x = 4 % 0

(e) >>> import math
  >>> i = math.sqrt(-1)

10-6. Improving open(). Create a wrapper for the open() function. When a program opens a file successfully, a file handle will be returned. If the file open fails, rather than generating an error, return None to the callers so that they can open files without an exception handler.

10-7. Exceptions. What is the difference between Python pseudocode snippets (a) and (b)? Answer in the context of statements A and B, which are part of both pieces of code. (Thanks to Guido for this teaser!)

image

10-8. Improving raw_input(). At the beginning of this chapter, we presented a “safe” version of the float() built-in function to detect and handle two different types of exceptions that float() generates. Likewise, the raw_input() function can generate two different exceptions, either EOFError or KeyboardInterrupt on end-of-file (EOF) or cancelled input, respectively. Create a wrapper function, perhaps safe_ input(); rather than raising an exception if the user entered EOF (^D in Unix or ^Z in DOS) or attempted to break out using ^C, have your function return None that the calling function can check for.

10-9. Improving math.sqrt(). The math module contains many functions and some constants for performing various mathematics-related operations. Unfortunately, this module does not recognize or operate on complex numbers, which is the reason why the cmath module was developed. Create a function, perhaps safe_sqrt(), which wraps math.sqrt(), but is smart enough to handle a negative parameter and return a complex number with the correct value back to the caller.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset