|I l@ve RuBoard|
There isn't much to trip over here, but here are a few general pointers on exception use.
As we've seen, when an exception is raised (by you or by Python itself), Python searches for the most recently entered try statement with a matching except clause, where matching means the same string object, the same class object, or a superclass of the raised class object. It's important to notice that matching is performed by identity, not equality. For instance, suppose we define two string objects we want to raise as exceptions:
>>> ex1 = "spam" >>> ex2 = "spam" >>> >>> ex1 == ex2, ex1 is ex2 (1, 0)
Applying the == test returns true (1) because they have equal values, but is returns false (0) since they are two distinct string objects in memory. Now, an except clause that names the same string object will always match:
>>> try: ... raise ex1 ... except ex1: ... print 'got it' ... got it
But one that lists an equal but not identical object will fail:
>>> try: ... raise ex1 ... except ex2: ... print 'Got it' ... Traceback (innermost last): File "<stdin>", line 2, in ? spam
Here, the exception isn't caught, so Python climbs to the top level of the process and prints a stack trace and the exception automatically (the string "spam"). For class exceptions, the behavior is similar, but Python generalizes the notion of exception matching to include superclass relationships.
Because Python lets you pick and choose which exceptions to catch, you sometimes have to be careful to not be too inclusive. For example, you've seen that an empty except clause catches every exception that might be raised while the code in the try block runs. Sometimes that's wanted, but you may also wind up intercepting an error that's expected by a try handler higher up in a system. An exception handler such as the following catches and stops every exception that reaches it, whether or not another handler is waiting for it:
try: ... except: ... # everything comes here!
The problem here is that you might not expect all the kinds of exceptions that could occur during an operation:
try: x = myditctionary[spam] # oops: misspelled except: x = None # assume we got KeyError or IndexError
In this case, you're assuming the only sort of error that can happen when indexing a dictionary is an indexing error. But because the name myditctionary is misspelled (you meant to say mydictionary), Python raises a NameError instead (since it's an undefined name reference), which will be silently caught and ignored by your handler. You should say: except (KeyError, IndexError): to make your intentions explicit.
Conversely, you sometimes need to not be so exclusive. When listing specific exceptions in a try, you catch only what you actually list. This isn't necessarily a bad thing either, but if a system evolves to raise other exceptions in the future, you may need to go back and add them to exception lists elsewhere in the code. For instance, the following handler is written to treat myerror1 and myerror2 as normal cases and treat everything else as an error. If a myerror3 is added in the future, it is processed as an error unless you update the exception list:
try: ... except (myerror1, myerror2): # what if I add a myerror3? ... # nonerrors else: ... # assumed to be an error
Careful use of class exceptions can make this gotcha go away completely. As we saw earlier in this chapter, if you catch a general superclass, you can add and raise more specific subclasses in the future without having to extend except clause lists manually.
Whether you use classes here or not, a little design goes a long way. The moral of the story is that you have to be careful not to be too general or too specific in exception handlers. Especially in larger systems, exception policies should be a part of the overall design.
|I l@ve RuBoard|