Learning Python - Mark Lutz [450]
AssertionError: Nobody expects the Spanish Inquisition!
User-Defined Exceptions
The raise statement introduced in the prior section raises a built-in exception defined in Python’s built-in scope. As you’ll learn later in this part of the book, you can also define new exceptions of your own that are specific to your programs. User-defined exceptions are coded with classes, which inherit from a built-in exception class: usually the class named Exception. Class-based exceptions allow scripts to build exception categories, inherit behavior, and have attached state information:
>>> class Bad(Exception): # User-defined exception
... pass
...
>>> def doomed():
... raise Bad() # Raise an instance
...
>>> try:
... doomed()
... except Bad: # Catch class name
... print('got Bad')
...
got Bad
>>>
Termination Actions
Finally, try statements can say “finally”—that is, they may include finally blocks. These look like except handlers for exceptions, but the try/finally combination specifies termination actions that always execute “on the way out,” regardless of whether an exception occurs in the try block:
>>> try:
... fetcher(x, 3)
... finally: # Termination actions
... print('after fetch')
...
'm'
after fetch
>>>
Here, if the try block finishes without an exception, the finally block will run, and the program will resume after the entire try. In this case, this statement seems a bit silly—we might as well have simply typed the print right after a call to the function, and skipped the try altogether:
fetcher(x, 3)
print('after fetch')
There is a problem with coding this way, though: if the function call raises an exception, the print will never be reached. The try/finally combination avoids this pitfall—when an exception does occur in a try block, finally blocks are executed while the program is being unwound:
>>> def after():
... try:
... fetcher(x, 4)
... finally:
... print('after fetch')
... print('after try?')
...
>>> after()
after fetch
Traceback (most recent call last):
File " File " File " IndexError: string index out of range >>> Here, we don’t get the “after try?” message because control does not resume after the try/finally block when an exception occurs. Instead, Python jumps back to run the finally action, and then propagates the exception up to a prior handler (in this case, to the default handler at the top). If we change the call inside this function so as not to trigger an exception, the finally code still runs, but the program continues after the try: >>> def after(): ... try: ... fetcher(x, 3) ... finally: ... print('after fetch') ... print('after try?') ... >>> after() after fetch after try? >>> In practice, try/except combinations are useful for catching and recovering from exceptions, and try/finally combinations come in handy to guarantee that termination actions will fire regardless of any exceptions that may occur in the try block’s code. For instance, you might use try/except to catch errors raised by code that you import from a third-party library, and try/finally to ensure that calls to close files or terminate server connections are always run. We’ll see some such practical examples later in this part of the book. Although they serve conceptually distinct purposes, as of Python 2.5, we can now mix except and finally clauses in the same try statement—the finally is run on the way out regardless of whether an exception was raised, and regardless of whether the exception was caught by an except clause. As we’ll learn in the next chapter, Python 2.6 and 3.0 provide an alternative to try/finally when using some types of objects. The with/as statement runs an object’s context management logic to guarantee that termination actions occur: >>> with open('lumberjack.txt', 'w') as file: # Always close file on exit ... file.write('The larch!\n') Although this option requires fewer lines of code, it’s only applicable when processing certain object types,