Related
This might be a silly question, so I did some research on these questions:
How do I raise the same Exception with a custom message in Python?
Proper way to declare custom exceptions in modern Python?
But none of these are matches what I'm trying to do for my CLI script, namely:
1.) If a certain Exception is raised, I want to re-raise the same Exception, with a tailored message instead of the default one.
2.) I am not looking to redefine a custom Exception type, just the same Exception.
3.) I am not looking to print a console text. I want to actually raise Exception because I need the exit code to be as close as possible as if the original Exception was raised since another process relies on this code.
4.) I want the error to be as short as possible, straight and to the point. A full trace back is not necessary.
So for example, these are what I've tried:
Attempt 1:
def func():
try:
1/0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!")
Result 1:
Traceback (most recent call last):
File "c:\Users\Dude\test.py", line 3, in <module>
1/0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\Users\Dude\test.py", line 5, in <module>
raise ZeroDivisionError("Don't do that you numbnut!")
ZeroDivisionError: Don't do that you numbnut!
[Done] exited with code=1 in 2.454 seconds
This meets my goal of #1, 2 and 3 are met, but the trace back is way too long... I don't need the original Exception at all, just the custom message.
Attempt 2:
def catch():
try:
1/0
return None
except ZeroDivisionError:
return ZeroDivisionError("Don't do that you numbnut!")
error = catch()
if error:
raise error
Result 2:
Traceback (most recent call last):
File "c:\Users\Dude\test.py", line 10, in <module>
raise error
ZeroDivisionError: Don't do that you numbnut!
[Done] exited with code=1 in 2.458 seconds
This gets me very close to what I want and is what I'm doing, however it feels quite unpythonic and pylint complains about the raise error line:
Raising NoneType while only classes or instances are allowed
pylint(raising-bad-type)
I also tried the methods in my linked questions, but they are unsatisfactory to all of my requirements as well. For the purpose of succinctness I have not included my attempts of those here.
My question is thus: is there a better, more obvious way to catch an Exception and re-raise it with a custom message that I'm simply missing?
This all seems quite unpythonic to me to begin with - but if it is really what you want why not raise from None in your first example in order not to get a larger traceback.
def func():
try:
1/0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!") from None
func()
Giving
Traceback (most recent call last):
File "/Users/dmodesitt/Dev/the.py", line 9, in <module>
func()
File "/Users/dmodesitt/Dev/the.py", line 6, in func
raise ZeroDivisionError("Don't do that you numbnut!") from None
ZeroDivisionError: Don't do that you numbnut!
This feature is called "chained exceptions" and was added in Python 3.
This block
try:
1 / 0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!")
>>>>
Traceback (most recent call last):
File "test123.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test123.py", line 4, in <module>
raise ZeroDivisionError("Don't do that you numbnut!")
ZeroDivisionError: Don't do that you numbnut!
Is similar to
try:
1 / 0
except ZeroDivisionError as e:
raise ZeroDivisionError("Don't do that you numbnut!") from e
=>>>>>>>>>
raceback (most recent call last):
File "test123.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test123.py", line 4, in <module>
raise ZeroDivisionError("Don't do that you numbnut!") from e
ZeroDivisionError: Don't do that you numbnut!
But you can disable exceptions chaining using from None statement.
try:
1 / 0
except ZeroDivisionError:
raise ZeroDivisionError("Don't do that you numbnut!") from None
=>>>>>>>>
Traceback (most recent call last):
File "test123.py", line 4, in <module>
raise ZeroDivisionError("Don't do that you numbnut!") from None
ZeroDivisionError: Don't do that you numbnut!
More information about the feature at https://www.python.org/dev/peps/pep-3134/
I was able to also use exit(...) to replicate very similar to what I want, but again it feels rather unpythonic:
def func():
try:
1/0
except ZeroDivisionError as err:
exit(f"{err.__class__.__name__}: Don't do that you numbnut!")
Result:
ZeroDivisionError: Don't do that you numbnut!
[Done] exited with code=1 in 3.433 seconds
For the purpose of my script, I think this might be the simplest solution. But from a wider stand point, I believe the other answers are better.
I just noticed that the following code written in Python 2.x doesn't go to the except-block
class MyException:
pass
try:
raise MyException()
except Exception:
print('Exception')
Output
Traceback (most recent call last): File "main.py", line 5, in
<module>
raise MyException()
__main__.MyException: <__main__.MyException instance at 0x021E9DC8>
while the following code does:
try:
raise 'str'
except Exception:
print('Exception')
Output
Exception
The same goes for raise 0 expression, for example.
Why? What is the reason behind this?
At least for python 2.7:
raise 'str' and raise 0 will both raise TypeError. This will be handled by the except Exception branch in your code.
Since your own class MyException doesn't inherit from Exception, the except branch is never executed. You could still do:
try:
raise MyException()
except MyException:
print('Exception')
In your example the output __main__.MyException: <__main__.MyException instance at 0x021E9DC8> was printed by sys.excepthook (the top-level exception handler), because you never caught the exception.
What's the difference between raise and raise from in Python?
try:
raise ValueError
except Exception as e:
raise IndexError
which yields
Traceback (most recent call last):
File "tmp.py", line 2, in <module>
raise ValueError
ValueError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "tmp.py", line 4, in <module>
raise IndexError
IndexError
and
try:
raise ValueError
except Exception as e:
raise IndexError from e
which yields
Traceback (most recent call last):
File "tmp.py", line 2, in <module>
raise ValueError
ValueError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "tmp.py", line 4, in <module>
raise IndexError from e
IndexError
The difference is that when you use from, the __cause__ attribute is set and the message states that the exception was directly caused by. If you omit the from then no __cause__ is set, but the __context__ attribute may be set as well, and the traceback then shows the context as during handling something else happened.
Setting the __context__ happens if you used raise in an exception handler; if you used raise anywhere else no __context__ is set either.
If a __cause__ is set, a __suppress_context__ = True flag is also set on the exception; when __suppress_context__ is set to True, the __context__ is ignored when printing a traceback.
When raising from a exception handler where you don't want to show the context (don't want a during handling another exception happened message), then use raise ... from None to set __suppress_context__ to True.
In other words, Python sets a context on exceptions so you can introspect where an exception was raised, letting you see if another exception was replaced by it. You can also add a cause to an exception, making the traceback explicit about the other exception (use different wording), and the context is ignored (but can still be introspected when debugging). Using raise ... from None lets you suppress the context being printed.
See the raise statement documenation:
The from clause is used for exception chaining: if given, the second expression must be another exception class or instance, which will then be attached to the raised exception as the __cause__ attribute (which is writable). If the raised exception is not handled, both exceptions will be printed:
>>> try:
... print(1 / 0)
... except Exception as exc:
... raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened
A similar mechanism works implicitly if an exception is raised inside an exception handler or a finally clause: the previous exception is then attached as the new exception’s __context__ attribute:
>>> try:
... print(1 / 0)
... except:
... raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened
Also see the Built-in Exceptions documentation for details on the context and cause information attached to exceptions.
PEP 3134, Exception Chaining and Embedded Tracebacks introduced chaining of exceptions (implicitly chained with explicit raise EXCEPTION or implicit raise, and explicitly chained with explicit raise EXCEPTION from CAUSE). Here are the relevant paragraphs to understand their usage:
Motivation
During the handling of one exception (exception A), it is possible that another exception (exception B) may occur. In today’s Python (version 2.4), if this happens, exception B is propagated outward and exception A is lost. In order to debug the problem, it is useful to know about both exceptions. The __context__ attribute retains this information automatically.
Sometimes it can be useful for an exception handler to intentionally re-raise an exception, either to provide extra information or to translate an exception to another type. The __cause__ attribute provides an explicit way to record the direct cause of an exception.
[…]
Implicit Exception Chaining
Here is an example to illustrate the __context__ attribute:
def compute(a, b):
try:
a/b
except Exception, exc:
log(exc)
def log(exc):
file = open('logfile.txt') # oops, forgot the 'w'
print >>file, exc
file.close()
Calling compute(0, 0) causes a ZeroDivisionError. The compute() function catches this exception and calls log(exc), but the log() function also raises an exception when it tries to write to a file that wasn’t opened for writing.
In today’s Python, the caller of compute() gets thrown an IOError. The ZeroDivisionError is lost. With the proposed change, the instance of IOError has an additional __context__ attribute that retains the ZeroDivisionError.
[…]
Explicit Exception Chaining
The __cause__ attribute on exception objects is always initialized to None. It is set by a new form of the raise statement:
raise EXCEPTION from CAUSE
which is equivalent to:
exc = EXCEPTION
exc.__cause__ = CAUSE
raise exc
In the following example, a database provides implementations for a few different kinds of storage, with file storage as one kind. The database designer wants errors to propagate as DatabaseError objects so that the client doesn’t have to be aware of the storage-specific details, but doesn’t want to lose the underlying error information.
class DatabaseError(Exception):
pass
class FileDatabase(Database):
def __init__(self, filename):
try:
self.file = open(filename)
except IOError, exc:
raise DatabaseError('failed to open') from exc
If the call to open() raises an exception, the problem will be reported as a DatabaseError, with a __cause__ attribute that reveals the IOError as the original cause.
Enhanced Reporting
The default exception handler will be modified to report chained exceptions. The chain of exceptions is traversed by following the __cause__ and __context__ attributes, with __cause__ taking priority. In keeping with the chronological order of tracebacks, the most recently raised exception is displayed last; that is, the display begins with the description of the innermost exception and backs up the chain to the outermost exception. The tracebacks are formatted as usual, with one of the lines:
The above exception was the direct cause of the following exception:
or
During handling of the above exception, another exception occurred:
between tracebacks, depending whether they are linked by __cause__ or __context__ respectively. Here is a sketch of the procedure:
def print_chain(exc):
if exc.__cause__:
print_chain(exc.__cause__)
print '\nThe above exception was the direct cause...'
elif exc.__context__:
print_chain(exc.__context__)
print '\nDuring handling of the above exception, ...'
print_exc(exc)
[…]
PEP 415, Implement Context Suppression with Exception Attributes then introduced suppression of exception contexts (with explicit raise EXCEPTION from None). Here is the relevant paragraph to understand its usage:
Proposal
A new attribute on BaseException, __suppress_context__, will be introduced. Whenever __cause__ is set, __suppress_context__ will be set to True. In particular, raise exc from cause syntax will set exc.__suppress_context__ to True. Exception printing code will check for that attribute to determine whether context and cause will be printed. __cause__ will return to its original purpose and values.
There is precedence for __suppress_context__ with the print_line_and_file exception attribute.
To summarize, raise exc from cause will be equivalent to:
exc.__cause__ = cause
raise exc
where exc.__cause__ = cause implicitly sets exc.__suppress_context__.
So in PEP 415, the sketch of the procedure given in PEP 3134 becomes the following:
def print_chain(exc):
if exc.__cause__:
print_chain(exc.__cause__)
print '\nThe above exception was the direct cause...'
elif exc.__context__ and not exc.__suppress_context__:
print_chain(exc.__context__)
print '\nDuring handling of the above exception, ...'
print_exc(exc)
The shortest answer. PEP-3134 says it all. raise Exception from e sets the __cause__ filed of the new exception.
A longer answer from the same PEP:
__context__ field would be set implicitly to the original error inside except: block unless told not to with __suppress_context__ = True.
__cause__ is just like context but has to be set explicitly by using from syntax
traceback will always chain when you call raise inside an except block. You can get rid of traceback by a) swallowing an exception except: pass or by messing with sys.exc_info() directly.
The long answer
import traceback
import sys
class CustomError(Exception):
def __init__(self):
super().__init__("custom")
def print_exception(func):
print(f"\n\n\nEXECURTING FUNCTION '{func.__name__}' \n")
try:
func()
except Exception as e:
"Here is result of our actions:"
print(f"\tException type: '{type(e)}'")
print(f"\tException message: '{e}'")
print(f"\tException context: '{e.__context__}'")
print(f"\tContext type: '{type(e.__context__)}'")
print(f"\tException cause: '{e.__cause__}'")
print(f"\tCause type: '{type(e.__cause__)}'")
print("\nTRACEBACKSTART>>>")
traceback.print_exc()
print("<<<TRACEBACKEND")
def original_error_emitter():
x = {}
print(x.does_not_exist)
def vanilla_catch_swallow():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
pass
def vanilla_catch_reraise():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
raise e
def catch_replace():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
raise CustomError()
def catch_replace_with_from():
"""Nothing is expected to happen"""
try:
original_error_emitter()
except Exception as e:
raise CustomError() from e
def catch_reset_trace():
saw_an_error = False
try:
original_error_emitter()
except Exception as e:
saw_an_error = True
if saw_an_error:
raise CustomError()
print("Note: This will print nothing")
print_exception(vanilla_catch_swallow)
print("Note: This will print AttributeError and 1 stack trace")
print_exception(vanilla_catch_reraise)
print("Note: This will print CustomError with no context but 2 stack traces")
print_exception(catch_replace)
print("Note: This will print CustomError with AttributeError context and 2 stack traces")
print_exception(catch_replace_with_from)
print("Note: This will brake traceback chain")
print_exception(catch_reset_trace)
Will result in the following output:
Note: This will print nothing
EXECURTING FUNCTION 'vanilla_catch_swallow'
Note: This will print AttributeError and 1 stack trace
EXECURTING FUNCTION 'vanilla_catch_reraise'
Exception type: '<class 'AttributeError'>'
Exception message: ''dict' object has no attribute 'does_not_exist''
Exception context: 'None'
Context type: '<class 'NoneType'>'
Exception cause: 'None'
Cause type: '<class 'NoneType'>'
TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 41, in vanilla_catch_reraise
raise e
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 39, in vanilla_catch_reraise
original_error_emitter()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'
<<<TRACEBACKEND
Note: This will print CustomError with no context but 2 stack traces
EXECURTING FUNCTION 'catch_replace'
Exception type: '<class '__main__.CustomError'>'
Exception message: 'custom'
Exception context: ''dict' object has no attribute 'does_not_exist''
Context type: '<class 'AttributeError'>'
Exception cause: 'None'
Cause type: '<class 'NoneType'>'
TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 46, in catch_replace
original_error_emitter()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 48, in catch_replace
raise CustomError()
CustomError: custom
<<<TRACEBACKEND
Note: This will print CustomError with AttributeError context and 2 stack traces
EXECURTING FUNCTION 'catch_replace_with_from'
Exception type: '<class '__main__.CustomError'>'
Exception message: 'custom'
Exception context: ''dict' object has no attribute 'does_not_exist''
Context type: '<class 'AttributeError'>'
Exception cause: ''dict' object has no attribute 'does_not_exist''
Cause type: '<class 'AttributeError'>'
TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 53, in catch_replace_with_from
original_error_emitter()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 27, in original_error_emitter
print(x.does_not_exist)
AttributeError: 'dict' object has no attribute 'does_not_exist'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 55, in catch_replace_with_from
raise CustomError() from e
CustomError: custom
<<<TRACEBACKEND
Note: This will brake traceback chain
EXECURTING FUNCTION 'catch_reset_trace'
Exception type: '<class '__main__.CustomError'>'
Exception message: 'custom'
Exception context: 'None'
Context type: '<class 'NoneType'>'
Exception cause: 'None'
Cause type: '<class 'NoneType'>'
TRACEBACKSTART>>>
Traceback (most recent call last):
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 11, in print_exception
func()
File "/Users/eugene.selivonchyk/repo/experiments/exceptions.py", line 64, in catch_reset_trace
raise CustomError()
CustomError: custom
<<<TRACEBACKEND
I have this try block in my code:
try:
do_something_that_might_raise_an_exception()
except ValueError as err:
errmsg = 'My custom error message.'
raise ValueError(errmsg)
Strictly speaking, I am actually raising another ValueError, not the ValueError thrown by do_something...(), which is referred to as err in this case. How do I attach a custom message to err? I try the following code but fails due to err, a ValueError instance, not being callable:
try:
do_something_that_might_raise_an_exception()
except ValueError as err:
errmsg = 'My custom error message.'
raise err(errmsg)
If you're lucky enough to only support python 3.x, this really becomes a thing of beauty :)
raise from
We can chain the exceptions using raise from.
try:
1 / 0
except ZeroDivisionError as e:
raise Exception('Smelly socks') from e
In this case, the exception your caller would catch has the line number of the place where we raise our exception.
Traceback (most recent call last):
File "test.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test.py", line 4, in <module>
raise Exception('Smelly socks') from e
Exception: Smelly socks
Notice the bottom exception only has the stacktrace from where we raised our exception. Your caller could still get the original exception by accessing the __cause__ attribute of the exception they catch.
with_traceback
Or you can use with_traceback.
try:
1 / 0
except ZeroDivisionError as e:
raise Exception('Smelly socks').with_traceback(e.__traceback__)
Using this form, the exception your caller would catch has the traceback from where the original error occurred.
Traceback (most recent call last):
File "test.py", line 2, in <module>
1 / 0
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test.py", line 4, in <module>
raise Exception('Smelly socks').with_traceback(e.__traceback__)
File "test.py", line 2, in <module>
1 / 0
Exception: Smelly socks
Notice the bottom exception has the line where we performed the invalid division as well as the line where we reraise the exception.
Update: For Python 3, check Ben's answer
To attach a message to the current exception and re-raise it:
(the outer try/except is just to show the effect)
For python 2.x where x>=6:
try:
try:
raise ValueError # something bad...
except ValueError as err:
err.message=err.message+" hello"
raise # re-raise current exception
except ValueError as e:
print(" got error of type "+ str(type(e))+" with message " +e.message)
This will also do the right thing if err is derived from ValueError. For example UnicodeDecodeError.
Note that you can add whatever you like to err. For example err.problematic_array=[1,2,3].
Edit: #Ducan points in a comment the above does not work with python 3 since .message is not a member of ValueError. Instead you could use this (valid python 2.6 or later or 3.x):
try:
try:
raise ValueError
except ValueError as err:
if not err.args:
err.args=('',)
err.args = err.args + ("hello",)
raise
except ValueError as e:
print(" error was "+ str(type(e))+str(e.args))
Edit2:
Depending on what the purpose is, you can also opt for adding the extra information under your own variable name. For both python2 and python3:
try:
try:
raise ValueError
except ValueError as err:
err.extra_info = "hello"
raise
except ValueError as e:
print(" error was "+ str(type(e))+str(e))
if 'extra_info' in dir(e):
print e.extra_info
It seems all the answers are adding info to e.args[0], thereby altering the existing error message. Is there a downside to extending the args tuple instead? I think the possible upside is, you can leave the original error message alone for cases where parsing that string is needed; and you could add multiple elements to the tuple if your custom error handling produced several messages or error codes, for cases where the traceback would be parsed programmatically (like via a system monitoring tool).
## Approach #1, if the exception may not be derived from Exception and well-behaved:
def to_int(x):
try:
return int(x)
except Exception as e:
e.args = (e.args if e.args else tuple()) + ('Custom message',)
raise
>>> to_int('12')
12
>>> to_int('12 monkeys')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')
or
## Approach #2, if the exception is always derived from Exception and well-behaved:
def to_int(x):
try:
return int(x)
except Exception as e:
e.args += ('Custom message',)
raise
>>> to_int('12')
12
>>> to_int('12 monkeys')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')
Can you see a downside to this approach?
This only works with Python 3. You can modify the exception's original arguments and add your own arguments.
An exception remembers the args it was created with. I presume this is so that you can modify the exception.
In the function reraise we prepend the exception's original arguments with any new arguments that we want (like a message). Finally we re-raise the exception while preserving the trace-back history.
def reraise(e, *args):
'''re-raise an exception with extra arguments
:param e: The exception to reraise
:param args: Extra args to add to the exception
'''
# e.args is a tuple of arguments that the exception with instantiated with.
#
e.args = args + e.args
# Recreate the exception and preserve the traceback info so that we can see
# where this exception originated.
#
raise e.with_traceback(e.__traceback__)
def bad():
raise ValueError('bad')
def very():
try:
bad()
except Exception as e:
reraise(e, 'very')
def very_very():
try:
very()
except Exception as e:
reraise(e, 'very')
very_very()
output
Traceback (most recent call last):
File "main.py", line 35, in <module>
very_very()
File "main.py", line 30, in very_very
reraise(e, 'very')
File "main.py", line 15, in reraise
raise e.with_traceback(e.__traceback__)
File "main.py", line 28, in very_very
very()
File "main.py", line 24, in very
reraise(e, 'very')
File "main.py", line 15, in reraise
raise e.with_traceback(e.__traceback__)
File "main.py", line 22, in very
bad()
File "main.py", line 18, in bad
raise ValueError('bad')
ValueError: ('very', 'very', 'bad')
try:
try:
int('a')
except ValueError as e:
raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
print err
prints:
There is a problem: invalid literal for int() with base 10: 'a'
This code template should allow you to raise an exception with a custom message.
try:
raise ValueError
except ValueError as err:
raise type(err)("my message")
This is the function I use to modify the exception message in Python 2.7 and 3.x while preserving the original traceback. It requires six
def reraise_modify(caught_exc, append_msg, prepend=False):
"""Append message to exception while preserving attributes.
Preserves exception class, and exception traceback.
Note:
This function needs to be called inside an except because
`sys.exc_info()` requires the exception context.
Args:
caught_exc(Exception): The caught exception object
append_msg(str): The message to append to the caught exception
prepend(bool): If True prepend the message to args instead of appending
Returns:
None
Side Effects:
Re-raises the exception with the preserved data / trace but
modified message
"""
ExceptClass = type(caught_exc)
# Keep old traceback
traceback = sys.exc_info()[2]
if not caught_exc.args:
# If no args, create our own tuple
arg_list = [append_msg]
else:
# Take the last arg
# If it is a string
# append your message.
# Otherwise append it to the
# arg list(Not as pretty)
arg_list = list(caught_exc.args[:-1])
last_arg = caught_exc.args[-1]
if isinstance(last_arg, str):
if prepend:
arg_list.append(append_msg + last_arg)
else:
arg_list.append(last_arg + append_msg)
else:
arg_list += [last_arg, append_msg]
caught_exc.args = tuple(arg_list)
six.reraise(ExceptClass,
caught_exc,
traceback)
Either raise the new exception with your error message using
raise Exception('your error message')
or
raise ValueError('your error message')
within the place where you want to raise it OR attach (replace) error message into current exception using 'from' (Python 3.x supported only):
except ValueError as e:
raise ValueError('your message') from e
Try below:
try:
raise ValueError("Original message. ")
except Exception as err:
message = 'My custom error message. '
# Change the order below to "(message + str(err),)" if custom message is needed first.
err.args = (str(err) + message,)
raise
Output:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
1 try:
----> 2 raise ValueError("Original message")
3 except Exception as err:
4 message = 'My custom error message.'
5 err.args = (str(err) + ". " + message,)
ValueError: Original message. My custom error message.
The current answer did not work good for me, if the exception is not re-caught the appended message is not shown.
But doing like below both keeps the trace and shows the appended message regardless if the exception is re-caught or not.
try:
raise ValueError("Original message")
except ValueError as err:
t, v, tb = sys.exc_info()
raise t, ValueError(err.message + " Appended Info"), tb
( I used Python 2.7, have not tried it in Python 3 )
Python 3 built-in exceptions have the strerror field:
except ValueError as err:
err.strerror = "New error message"
raise err
None of the above solutions did exactly what I wanted, which was to add some information to the first part of the error message i.e. I wanted my users to see my custom message first.
This worked for me:
exception_raised = False
try:
do_something_that_might_raise_an_exception()
except ValueError as e:
message = str(e)
exception_raised = True
if exception_raised:
message_to_prepend = "Custom text"
raise ValueError(message_to_prepend + message)
Python 3.11+
PEP 678 – Enriching Exceptions with Notes was accepted and landed in Python 3.11. New APIs allow users to attach custom message(s) to existing errors. This is useful for adding additional context when an error is encountered.
Using the add_note method is suitable for answering the original question:
try:
int("eleven")
except ValueError as e:
errmsg = "My custom error message."
e.add_note(errmsg)
raise
It would render like this:
Traceback (most recent call last):
File "/tmp/example.py", line 2, in <module>
int("eleven")
ValueError: invalid literal for int() with base 10: 'eleven'
My custom error message.
Python < 3.11
Modifying the args attribute, which is used by BaseException.__str__ to render an exception, is the only way. You could either extend the args:
try:
int("eleven")
except ValueError as e:
errmsg = "My custom error message."
e.args += (errmsg,)
raise e
Which will render as:
Traceback (most recent call last):
File "/tmp/example.py", line 2, in <module>
int("eleven")
ValueError: ("invalid literal for int() with base 10: 'eleven'", 'My custom error message.')
Or you could replace the args[0], which is a little more complicated but produces a cleaner result.
try:
int("eleven")
except ValueError as e:
errmsg = "My custom error message."
args = e.args
if not args:
arg0 = errmsg
else:
arg0 = f"{args[0]}\n{errmsg}"
e.args = (arg0,) + args[1:]
raise
This will render the same way as the Python 3.11+ exception __notes__ do:
Traceback (most recent call last):
File "/tmp/example.py", line 2, in <module>
int("eleven")
ValueError: invalid literal for int() with base 10: 'eleven'
My custom error message.
I tried this compact version of #RobinL, and worked as well:
try:
do_something_that_might_raise_an_exception()
except ValueError as e:
raise ValueError(f'Custom text {e}')
Raising same error, with prepending custom text message in front.
(edit - sorry, actually same as https://stackoverflow.com/a/65494175/15229310 , why there is like 10 (upvoted) 'solutions' that simply don't answer question as posted?)
try:
<code causing exception>
except Exception as e:
e.args = (f"My custom text. Original Exception text: {'-'.join(e.args)}",)
raise
Many of proposed solutions above re-raising an exception again, which is considered as a bad practice. Something simple like this will do the job
try:
import settings
except ModuleNotFoundError:
print("Something meaningfull\n")
raise
So You'll print the error message first, and then raise the stack trace, or you can simply exit by sys.exit(1) and not show the error message at all.
if you want to custom the error type, a simple thing you can do is to define an error class based on ValueError.
I have a bit of code that does some functional exception handling and everything works well, exceptions are raised when I want them to be, but when I'm debugging, the line-traces don't always do quite what I want them to.
Example A:
>>> 3/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Example B:
>>> try: 3/0
... except Exception as e: raise e
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
In both of these examples, the exception really occurs in line 1, where we attempt to do 3/0, but in the latter example, we are told it has occurred on line 2, where it is raised.
Is there a way in Python to raise an exception, as if it were another exception, something that would produce the following output:
>>> try: 3/0
... except Exception as e: metaraise(e)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
When you re-raise an exception that you caught, such as
except Exception as e: raise e
it resets the stack trace. It's just like re-raising a new exception. What you want is this:
except Exception as e: raise
For reference, the solution is approximately as follows:
def getException():
return sys.exc_info()
def metaraise(exc_info):
raise exc_info[0], exc_info[1], exc_info[2]
try: 3/0
except:
e = getException()
metaraise(e)
The beautiful part of this is that you can then pass around the variable e and metaraise it somewhere else, even if other exceptions have been encountered along the way.