views:

263

answers:

3

I'm writing a module and want to have a unified exception hierarchy for the exceptions that it can raise. This allows users of the module to catch those particular exceptions and handle them distinctly, if needed. But many of the exceptions raised from the module are raised because of some other exception; e.g. failing at some task because of an OSError on a file.

What I need is to “wrap” the exception caught such that it has a different type and message, so that information is available further up the propagation hierarchy by whatever catches the exception. But I don't want to lose the existing type, message, and stack trace; that's all useful information for someone trying to debug the problem. A top-level exception handler is no good, since I'm trying to decorate the exception before it makes its way further up the propagation stack, and the top-level handler is too late.

This is partly solved by deriving my module's specific exception types from the existing type (e.g. ‘class FooPermissionError(OSError)’), but that doesn't make it any easier to wrap the existing exception instance in a new type, nor modify the message.

Python's PEP 3134 “Exception Chaining and Embedded Tracebacks” discusses a change accepted in Python 3.0 for “chaining” exception objects, to indicate that a new exception was raised during the handling of an existing exception.

What I'm trying to do is related: I need it working in earlier Python versions, and I need it not for chaining, but only for polymorphism. What is the right way to do this?

+4  A: 

You could create your own exception type that extends whichever exception you've caught.

class NewException(CaughtException):
    def __init__(self, caught):
        self.caught = caught

try:
    ...
except CaughtException as e:
    ...
    raise NewException(e)

But most of the time, I think it would be simpler to catch the exception, handle it, and either raise the original exception (and preserve the traceback) or raise NewException(). If I were calling your code, and I received one of your custom exceptions, I'd expect that your code has already handled whatever exception you had to catch. Thus I don't need to access it myself.

Edit: I found this analysis of ways to throw your own exception and keep the original exception. No pretty solutions.

Nikhil Chelliah
The use case I've described isn't for *handling* the exception; it's specifically about *not* handling it, but adding some extra information (an additional class and a new message) so that it can be handled further up the call stack.
bignose
+1  A: 

You can use sys.exc_info() to get the traceback, and raise your new exception with said traceback (as the PEP mentions). If you want to preserve the old type and message, you can do so on the exception, but that's only useful if whatever catches your exception looks for it.

For example

import sys

def failure():
    try: 1/0
    except ZeroDivisionError, e:
        type, value, traceback = sys.exc_info()
        raise ValueError, ("You did something wrong!", type, value), traceback

Of course, this is really not that useful. If it was, we wouldn't need that PEP. I'd not recommend doing it.

Devin Jeanpierre
Devin, you store a reference to the traceback there, shouldn't you be explicitly deleting that reference?
Arafangion
I didn't store anything, I left traceback as a local variable that presumably falls out of scope. Yes, it is conceivable that it doesn't, but if you raise exceptions like that in the global scope rather than within functions, you've got bigger issues. If your complaint is only that it could be executed in a global scope, the proper solution is not to add irrelevant boilerplate that has to be explained and isn't relevant for 99% of uses, but to rewrite the solution so that no such thing is necessary while making it seem as if nothing is different-- as I have now done.
Devin Jeanpierre
+1  A: 

It appears this use case has no good answer currently, as described by Ian Bicking. Bummer.

bignose