Jul 15, 2009
Python Exception Handling: Cleanup and Reraise
I’ve had this code around for a while and had an opportunity to drag it out the other day and dust it off. The problem: Every now and again there’s a situation where you don’t really want to catch an exception, but you do want to perform some cleanup and let the exception propagate up the stack. Sometimes there’s an extra wrinkle in that the cleanup code may itself throw an exception (that I’m simply going to assume we can ignore).
You can run the file to see the behavior. Simply provide an integer 1-5 as a command line argument and you’ll run the selected scenario and see the output. The goal in this case is for cleanup to occur and an exception to be reported as having occurred at line 10.
The code in reraiser1 is wrong because this behaves as if a brand new exception were thrown at line 27. That may not seem so bad, but this code is pretty simple. If this happens and the stack trace is deep, it will be almost impossible to diagnose what went wrong.
The code in reraiser2 shows what happens when a second exception occurs in the except block. A bare raise statement here might be an attempt to re-raise the original exception, but python’s rules about re-raising specify that the most recent exception in the scope is what is reraised. In this case, that’s the exception thrown from the cleanup function. Again, this makes troubleshooting difficult.
In reraiser3 I worked around the problem in reraiser2 by moving the cleanup function’s exception into a separate scope by defining a local cleanup function and calling it from within the except block. This prevents the cleanup function’s exception from polluting the scope with an irrelevant exception and we can re-raise the original exception. This results in a stack trace rooted at line 10.
Reraiser4 takes a different approach. Instead of moving the cleanup function’s exception into a separate scope, it captures the traceback information from the original exception and then passes it back to the raise statement so that the reported traceback is accurate.
The cleanest way to handle this is to use a finally block as shown in reraiser5. This situation is what “finally” is meant for: it does not trap the exception, it just gives you a chance to clean up before control moves back up the stack to the caller. The presence of finally clues readers in to the fact that you aren’t messing with the exception, and that the point of the block is to perform cleanup.
Kindly drop me a note if I’ve got something wrong above, or if I’m missing a technique (or a common anti-pattern!). Thanks.
Related posts:
- Using Python’s ctypes to Call Into C Libraries The ctypes module makes loading and calling into a dynamic...
- Five Days to a Django Web App: Day Three, Coding Thanks for coming back for Day Three! [Note: Sorry...
- Yet Another Python Enum Module I didn’t like the existing enum recipes, so I...
- Jesse Noller on Python Jesse Noller has been republishing articles he wrote for...
- Five Days to a Django Web App: Day Four, Deployment Thanks for your patience, and for coming back for...
I guess the point of my comment is to affirm your thinking here: I hadn’t done a lot of coding with exception handling before this past year, but for a project that I was working on (in Java) I eventually arrived at the same pattern that you did here: using “finally” is the way to go, in my opinion. It is refreshing to arrive at a clean code pattern for problems like this, in contrast to some of the messier solutions (or, in fact, buggy code…) that I have seen elsewhere…
Haridara left a comment on my (now abandoned — a short lived experiment) posterous version of this blog.
I am quoting here because it includes a helpful link and useful tips: