Learning Python - Mark Lutz [476]
At the end of this chapter, we’ll also meet some more complete testing frameworks provided by Python, such as doctest and PyUnit, which provide tools for comparing expected outputs with actual results.
More on sys.exc_info
The sys.exc_info result used in the last two sections allows an exception handler to gain access to the most recently raised exception generically. This is especially useful when using the empty except clause to catch everything blindly, to determine what was raised:
try:
...
except:
# sys.exc_info()[0:2] are the exception class and instance
If no exception is being handled, this call it returns a tuple containing three None values. Otherwise, the values returned are (type, value, traceback), where:
type is the exception class of the exception being handled.
value is the exception class instance that was raised.
traceback is a traceback object that represents the call stack at the point where the exception originally occurred (see the traceback module’s documentation for tools that may be used in conjunction with this object to generate error messages manually).
As we saw in the prior chapter, sys.exc_info can also sometimes be useful to determine the specific exception type when catching exception category superclasses. As we saw, though, because in this case you can also get the exception type by fetching the __class__ attribute of the instance obtained with the as clause, sys.exc_info is mostly used by the empty except today:
try:
...
except General as instance:
# instance.__class__ is the exception class
That said, using the instance object’s interfaces and polymorphism is often a better approach than testing exception types—exception methods can be defined per class and run generically:
try:
...
except General as instance:
# instance.method() does the right thing for this instance
As usual, being too specific in Python can limit your code’s flexibility. A polymorphic approach like the last example here generally supports future evolution better.
* * *
Note
Version skew note: In Python 2.6, the older tools sys.exc_type and sys.exc_value still work to fetch the most recent exception type and value, but they can manage only a single, global exception for the entire process. These two names have been removed in Python 3.0. The newer and preferred sys.exc_info() call available in both 2.6 and 3.0 instead keeps track of each thread’s exception information, and so is thread-specific. Of course, this distinction matters only when using multiple threads in Python programs (a subject beyond this book’s scope), but 3.0 forces the issue. See other resources for more details.
* * *
Exception Design Tips and Gotchas
I’m lumping design tips and gotchas together in this chapter, because it turns out that the most common gotchas largely stem from design issues. By and large, exceptions are easy to use in Python. The real art behind them is in deciding how specific or general your except clauses should be and how much code to wrap up in try statements. Let’s address the second of these concerns first.
What Should Be Wrapped
In principle, you could wrap every statement in your script in its own try, but that would just be silly (the try statements would then need to be wrapped in try statements!). What to wrap is really a design issue that goes beyond the language itself, and it will become more apparent with use. But for now, here are a few rules of thumb:
Operations that commonly fail should generally be wrapped in try statements. For example, operations that interface with system state (file opens, socket calls, and the like) are prime candidates for trys.
However, there are exceptions to the prior rule—in a simple script, you may want failures of such operations to kill your program instead of being caught and ignored. This is especially true if the failure is a showstopper. Failures in Python typically result in useful error messages (not hard crashes), and this is often the best outcome you could hope