Re-raise exception with a different type and message, preserving existing information
Python 3 introduced exception chaining (as described in PEP 3134). This allows, when raising an exception, to cite an existing exception as the “cause”:
try:
frobnicate()
except KeyError as exc:
raise ValueError("Bad grape") from exc
The caught exception (exc
, a KeyError) thereby becomes part of (is the “cause of”) the new exception, a ValueError. The “cause” is available to whatever code catches the new exception.
By using this feature, the __cause__
attribute is set. The built-in exception handler also knows how to report the exception's “cause” and “context” along with the traceback.
In Python 2, it appears this use case has no good answer (as described by Ian Bicking and Ned Batchelder). Bummer.
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.
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.
I also found that many times i need some "wrapping" to errors raised.
This included both in a function scope and sometimes wrap only some lines inside a function.
Created a wrapper to be used a decorator
and context manager
:
Implementation
import inspect
from contextlib import contextmanager, ContextDecorator
import functools
class wrap_exceptions(ContextDecorator):
def __init__(self, wrapper_exc, *wrapped_exc):
self.wrapper_exc = wrapper_exc
self.wrapped_exc = wrapped_exc
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
if not exc_type:
return
try:
raise exc_val
except self.wrapped_exc:
raise self.wrapper_exc from exc_val
def __gen_wrapper(self, f, *args, **kwargs):
with self:
for res in f(*args, **kwargs):
yield res
def __call__(self, f):
@functools.wraps(f)
def wrapper(*args, **kw):
with self:
if inspect.isgeneratorfunction(f):
return self.__gen_wrapper(f, *args, **kw)
else:
return f(*args, **kw)
return wrapper
Usage examples
decorator
@wrap_exceptions(MyError, IndexError)
def do():
pass
when calling do
method, don't worry about IndexError
, just MyError
try:
do()
except MyError as my_err:
pass # handle error
context manager
def do2():
print('do2')
with wrap_exceptions(MyError, IndexError):
do()
inside do2
, in the context manager
, if IndexError
is raised, it will
be wrapped and raised MyError