Related
In the module warnings (https://docs.python.org/3.5/library/warnings.html) there is the ability to raise a warning that appears to come from somewhere earlier in the stack:
warnings.warn('This is a test', stacklevel=2)
Is there an equivalent for raising errors? I know I can raise an error with an alternative traceback, but I can't create that traceback within the module since it needs to come from earlier. I imagine something like:
tb = magic_create_traceback_right_here()
raise ValueError('This is a test').with_traceback(tb.tb_next)
The reason is that I am developing a module that has a function module.check_raise that I want to raise an error that appears to originate from where the function is called. If I raise an error within the module.check_raise function, it appears to originate from within module.check_raise, which is undesired.
Also, I've tried tricks like raising a dummy exception, catching it, and passing the traceback along, but somehow the tb_next becomes None. I'm out of ideas.
Edit:
I would like the output of this minimal example (called tb2.py):
import check_raise
check_raise.raise_if_string_is_true('True')
to be only this:
Traceback (most recent call last):
File "tb2.py", line 10, in <module>
check_raise.raise_if_string_is_true(string)
RuntimeError: An exception was raised.
I can't believe I am posting this
By doing this you are going against the zen.
Special cases aren't special enough to break the rules.
But if you insist here is your magical code.
check_raise.py
import sys
import traceback
def raise_if_string_is_true(string):
if string == 'true':
#the frame that called this one
f = sys._getframe().f_back
#the most USELESS error message ever
e = RuntimeError("An exception was raised.")
#the first line of an error message
print('Traceback (most recent call last):',file=sys.stderr)
#the stack information, from f and above
traceback.print_stack(f)
#the last line of the error
print(*traceback.format_exception_only(type(e),e),
file=sys.stderr, sep="",end="")
#exit the program
#if something catches this you will cause so much confusion
raise SystemExit(1)
# SystemExit is the only exception that doesn't trigger an error message by default.
This is pure python, does not interfere with sys.excepthook and even in a try block it is not caught with except Exception: although it is caught with except:
test.py
import check_raise
check_raise.raise_if_string_is_true("true")
print("this should never be printed")
will give you the (horribly uninformative and extremely forged) traceback message you desire.
Tadhgs-MacBook-Pro:Documents Tadhg$ python3 test.py
Traceback (most recent call last):
File "test.py", line 3, in <module>
check_raise.raise_if_string_is_true("true")
RuntimeError: An exception was raised.
Tadhgs-MacBook-Pro:Documents Tadhg$
If I understand correctly, you would like the output of this minimal example:
def check_raise(function):
try:
return function()
except Exception:
raise RuntimeError('An exception was raised.')
def function():
1/0
check_raise(function)
to be only this:
Traceback (most recent call last):
File "tb2.py", line 10, in <module>
check_raise(function)
RuntimeError: An exception was raised.
In fact, it's a lot more output; there is exception chaining, which could be dealt with by handling the RuntimeError immediately, removing its __context__, and re-raising it, and there is another line of traceback for the RuntimeError itself:
File "tb2.py", line 5, in check_raise
raise RuntimeError('An exception was raised.')
As far as I can tell, it is not possible for pure Python code to substitute the traceback of an exception after it was raised; the interpreter has control of adding to it but it only exposes the current traceback whenever the exception is handled. There is no API (not even when using tracing functions) for passing your own traceback to the interpreter, and traceback objects are immutable (this is what's tackled by that Jinja hack involving C-level stuff).
So further assuming that you're interested in the shortened traceback not for further programmatic use but only for user-friendly output, your best bet will be an excepthook that controls how the traceback is printed to the console. For determining where to stop printing, a special local variable could be used (this is a bit more robust than limiting the traceback to its length minus 1 or such). This example requires Python 3.5 (for traceback.walk_tb):
import sys
import traceback
def check_raise(function):
__exclude_from_traceback_from_here__ = True
try:
return function()
except Exception:
raise RuntimeError('An exception was raised.')
def print_traceback(exc_type, exc_value, tb):
for i, (frame, lineno) in enumerate(traceback.walk_tb(tb)):
if '__exclude_from_traceback_from_here__' in frame.f_code.co_varnames:
limit = i
break
else:
limit = None
traceback.print_exception(
exc_type, exc_value, tb, limit=limit, chain=False)
sys.excepthook = print_traceback
def function():
1/0
check_raise(function)
This is the output now:
Traceback (most recent call last):
File "tb2.py", line 26, in <module>
check_raise(function)
RuntimeError: An exception was raised.
EDIT: The previous version did not provide quotes or explanations.
I suggest referring to PEP 3134 which states in the Motivation:
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.
When an Exception is raised with a __cause__ attribute the traceback message takes the form of:
Traceback (most recent call last):
<CAUSE TRACEBACK>
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
<MAIN TRACEBACK>
To my understanding this is exactly what you are trying to accomplish; clearly indicate that the reason for the error is not your module but somewhere else. If you are instead trying to omit information to the traceback like your edit suggests then the rest of this answer won't do you any good.
Just a note on syntax:
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
so the bare minimum example would be something like this:
def function():
int("fail")
def check_raise(function):
try:
function()
except Exception as original_error:
err = RuntimeError("An exception was raised.")
raise err from original_error
check_raise(function)
which gives an error message like this:
Traceback (most recent call last):
File "/PATH/test.py", line 7, in check_raise
function()
File "/PATH/test.py", line 3, in function
int("fail")
ValueError: invalid literal for int() with base 10: 'fail'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/PATH/test.py", line 12, in <module>
check_raise(function)
File "/PATH/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: An exception was raised.
However the first line of the cause is the statement in the try block of check_raise:
File "/PATH/test.py", line 7, in check_raise
function()
so before raising err it may (or may not) be desirable to remove the outer most traceback frame from original_error:
except Exception as original_error:
err = RuntimeError("An exception was raised.")
original_error.__traceback__ = original_error.__traceback__.tb_next
raise err from original_error
This way the only line in the traceback that appears to come from check_raise is the very last raise statement which cannot be omitted with pure python code although depending on how informative the message is you can make it very clear that your module was not the cause of the problem:
err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))
The advantage to raising exception like this is that the original Traceback message is not lost when the new error is raised, which means that a very complex series of exceptions can be raised and python will still display all the relevant information correctly:
def check_raise(function):
try:
function()
except Exception as original_error:
err = RuntimeError("""{0.__qualname__} encountered an error during call to {1.__module__}.{1.__name__}
the traceback for the error is shown above.""".format(function,check_raise))
original_error.__traceback__ = original_error.__traceback__.tb_next
raise err from original_error
def test_chain():
check_raise(test)
def test():
raise ValueError
check_raise(test_chain)
gives me the following error message:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 16, in test
raise ValueError
ValueError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 13, in test_chain
check_raise(test)
File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: test encountered an error during call to __main__.check_raise
the traceback for the error is shown above.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 18, in <module>
check_raise(test_chain)
File "/Users/Tadhg/Documents/test.py", line 10, in check_raise
raise err from original_error
RuntimeError: test_chain encountered an error during call to __main__.check_raise
the traceback for the error is shown above.
Yes it is long but it is significantly more informative then:
Traceback (most recent call last):
File "/Users/Tadhg/Documents/test.py", line 18, in <module>
check_raise(test_chain)
RuntimeError: An exception was raised.
not to mention that the original error is still usable even if the program doesn't end:
import traceback
def check_raise(function):
...
def fail():
raise ValueError
try:
check_raise(fail)
except RuntimeError as e:
cause = e.__cause__
print("check_raise failed because of this error:")
traceback.print_exception(type(cause), cause, cause.__traceback__)
print("and the program continues...")
I understand 'Don't do this'. On the other hand, there may be some special use cases i believe. I'm generating own errors (just deleting some defined frames...) this way
def get_traceback_with_removed_frames_by_line_string(lines):
"""In traceback call stack, it is possible to remove particular level defined by some line content.
Args:
lines (list): Line in call stack that we want to hide.
Returns:
string: String traceback ready to be printed.
"""
exc = trcbck.TracebackException(*sys.exc_info())
for i in exc.stack[:]:
if i.line in lines:
exc.stack.remove(i)
return "".join(exc.format())
I return just string.
If there is concrete function that is raising, you can add it to ignored frames.
Though have in mind, that if you hide something, somebody may not understand why is something happening...
My use case was to hide only first level - decorator from my library that was decorating all user functions in framework, so error from user side was on level 1.
What does raise do, if it's not inside a try or except clause, but simply as the last statement in the function?
def foo(self):
try:
# some code that raises an exception
except Exception as e:
pass
# notice that the "raise" is outside
raise
This example prints 1 but not 2 so it must be that the last raise statement simply raises the last thrown exception.
def foo():
try:
raise Exception()
except Exception as e:
pass
print 1
raise
print 2
if __name__ == '__main__':
foo()
Any official documentation for this type of usage pattern?
As Russell said,
A bare raise statement re-raises the last caught exception.
It doesn't matter whether this is happening in a try-except block or not. If there has been a caught exception, then calling raise will re-raise that exception. Otherwise, Python will complain that the previously caught exception is None and raise a TypeError because None is not something that can actually be raised.
As tdelaney said, it doesn't seem to make sense to do this except in an error-handling function. Personally I'd say that it doesn't even belong in an error-handling function, as the raise should still be in the except clause. Someone could use this in an attempt to execute code whether or not an error occurs, but a finally clause is the proper way to do that. Another possibility would be using this as a way to determine if an error occurred while executing the function, but there are much better ways to do that (such as returning an extra value that indicates if/where an error occurred).
A Bare raise reraises the current exception. This usually makes no sense at the end of a function, unless the function is called in an exception:
By itself, the raise is invalid and python throws its own exception
>>> def x():
... raise
>>> x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in x
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType
But if called within an exception block, it acts sanely
>>> try:
... int('a')
... except:
... x()
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
File "<stdin>", line 2, in <module>
ValueError: invalid literal for int() with base 10: 'a'
>>>
EDIT
This might be a perfectly reasonable thing to do if the function is attempting some sort of recovery. The function could fix what's broken, log a message, trigger the fire extinguishers, etc... and raise if it still thinks the system is in error.
A bare raise statement re-raises the last caught exception. https://docs.python.org/2/tutorial/errors.html#raising-exceptions
From this documentation we can read:
If no expressions are present, raise re-raises the last exception that
was active in the current scope. If no exception is active in the
current scope, a TypeError exception is raised indicating that this is
an error (if running under IDLE, a Queue.Empty exception is raised
instead).
This means that, in the case of your code, if no exception occurs within the try ... except block, then you are forcing the program to raise a TypeError exception to happen.
I had a problem like this where I needed to raise a previously caught exception outside the try/except block if my function didn't return a value. I did a bit of looking around in the sys and traceback modules, but couldn't find a good method to do this, so I just ended up storing the exception outside the block.
def foo():
caught = None
try:
raise Exception
except Exception as e:
caught = e
pass
raise caught
f = foo()
Output
Traceback (most recent call last):
line 13, in <module>
line 10, in foo
line 5, in foo
Exception
Clearly this isn't useful in the above example, but it's pretty useful if you need to try something quite a few times in a loop and re-raise. My specific need was for an HTTP request retry mechanism.
import time
def foo(key):
caught = None
for i in [1, 2, 3, 4, 5]:
try:
return d[key]
except KeyError as e:
caught = e
print(i)
time.sleep(i)
continue
raise caught
d = {"bar": "baz"}
f = foo(key="baz")
Output
1
2
3
4
5
Traceback (most recent call last):
line 19, in <module>
line 15, in foo
line 8, in foo
KeyError: 'baz'
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'm trying to catch an exception in a thread and re-raise it in the main thread:
import threading
import sys
class FailingThread(threading.Thread):
def run(self):
try:
raise ValueError('x')
except ValueError:
self.exc_info = sys.exc_info()
failingThread = FailingThread()
failingThread.start()
failingThread.join()
print failingThread.exc_info
raise failingThread.exc_info[1]
This basically works and yields the following output:
(<type 'exceptions.ValueError'>, ValueError('x',), <traceback object at 0x1004cc320>)
Traceback (most recent call last):
File "test.py", line 16, in <module>
raise failingThread.exc_info[1]
However, the source of the exception points to line 16, where the re-raise occurred. The original exception comes from line 7. How do I have to modify the main thread so that the output reads:
Traceback (most recent call last):
File "test.py", line 7, in <module>
In Python 2 you need to use all three arguments to raise:
raise failingThread.exc_info[0], failingThread.exc_info[1], failingThread.exc_info[2]
passing the traceback object in as the third argument preserves the stack.
From help('raise'):
If a third object is present and not None, it must be a traceback
object (see section The standard type hierarchy), and it is
substituted instead of the current location as the place where the
exception occurred. If the third object is present and not a
traceback object or None, a TypeError exception is raised. The
three-expression form of raise is useful to re-raise an exception
transparently in an except clause, but raise with no expressions
should be preferred if the exception to be re-raised was the most
recently active exception in the current scope.
In this particular case you cannot use the no expression version.
For Python 3 (as per the comments):
raise failingThread.exc_info[1].with_traceback(failingThread.exc_info[2])
or you can simply chain the exceptions using raise ... from ... but that raises a chained exception with the original context attached in the cause attribute and that may or may not be what you want.
This code snippet works in both python 2 & 3:
1 try:
----> 2 raise KeyError('Default key error message')
3 except KeyError as e:
4 e.args = ('Custom message when get re-raised',) #The comma is not a typo, it's there to indicate that we're replacing the tuple that e.args pointing to with another tuple that contain the custom message.
5 raise
This question already has answers here:
How do I raise the same Exception with a custom message in Python?
(17 answers)
Why doesn't it work to append information in the exception message?
(2 answers)
Closed 2 days ago.
I want to achieve something like this:
def foo():
try:
raise IOError('Stuff ')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
e.message = e.message + 'happens at %s' % arg1
raise
bar('arg1')
Traceback...
IOError('Stuff Happens at arg1')
But what I get is:
Traceback..
IOError('Stuff')
Any clues as to how to achieve this? How to do it both in Python 2 and 3?
In case you came here searching for a solution for Python 3 the manual says:
When raising a new exception (rather than using a bare raise to re-raise the exception currently being handled), the implicit exception context can be supplemented with an explicit cause by using from with raise:
raise new_exc from original_exc
Example:
try:
return [permission() for permission in self.permission_classes]
except TypeError as e:
raise TypeError("Make sure your view's 'permission_classes' are iterable. "
"If you use '()' to generate a set with a single element "
"make sure that there is a comma behind the one (element,).") from e
Which looks like this in the end:
2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
# Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If
you use parens () to generate a set with a single element make
sure that there is a (comma,) behind the one element.
Turning a totally nondescript TypeError into a nice message with hints towards a solution without messing up the original Exception.
I'd do it like this so changing its type in foo() won't require also changing it in bar().
def foo():
try:
raise IOError('Stuff')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
raise type(e)(e.message + ' happens at %s' % arg1)
bar('arg1')
Traceback (most recent call last):
File "test.py", line 13, in <module>
bar('arg1')
File "test.py", line 11, in bar
raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1
Update 1
Here's a slight modification that preserves the original traceback:
...
def bar(arg1):
try:
foo()
except Exception as e:
import sys
raise type(e), type(e)(e.message +
' happens at %s' % arg1), sys.exc_info()[2]
bar('arg1')
Traceback (most recent call last):
File "test.py", line 16, in <module>
bar('arg1')
File "test.py", line 11, in bar
foo()
File "test.py", line 5, in foo
raise IOError('Stuff')
IOError: Stuff happens at arg1
Update 2
For Python 3.x, the code in my first update is syntactically incorrect plus the idea of having a message attribute on BaseException was retracted in a change to PEP 352 on 2012-05-16 (my first update was posted on 2012-03-12). So currently, in Python 3.5.2 anyway, you'd need to do something along these lines to preserve the traceback and not hardcode the type of exception in function bar(). Also note that there will be the line:
During handling of the above exception, another exception occurred:
in the traceback messages displayed.
# for Python 3.x
...
def bar(arg1):
try:
foo()
except Exception as e:
import sys
raise type(e)(str(e) +
' happens at %s' % arg1).with_traceback(sys.exc_info()[2])
bar('arg1')
Update 3
A commenter asked if there was a way that would work in both Python 2 and 3. Although the answer might seem to be "No" due to the syntax differences, there is a way around that by using a helper function like reraise() in the six add-on module. So, if you'd rather not use the library for some reason, below is a simplified standalone version.
Note too, that since the exception is reraised within the reraise() function, that will appear in whatever traceback is raised, but the final result is what you want.
import sys
if sys.version_info.major < 3: # Python 2?
# Using exec avoids a SyntaxError in Python 3.
exec("""def reraise(exc_type, exc_value, exc_traceback=None):
raise exc_type, exc_value, exc_traceback""")
else:
def reraise(exc_type, exc_value, exc_traceback=None):
if exc_value is None:
exc_value = exc_type()
if exc_value.__traceback__ is not exc_traceback:
raise exc_value.with_traceback(exc_traceback)
raise exc_value
def foo():
try:
raise IOError('Stuff')
except:
raise
def bar(arg1):
try:
foo()
except Exception as e:
reraise(type(e), type(e)(str(e) +
' happens at %s' % arg1), sys.exc_info()[2])
bar('arg1')
Assuming you don't want to or can't modify foo(), you can do this:
try:
raise IOError('stuff')
except Exception as e:
if len(e.args) >= 1:
e.args = (e.args[0] + ' happens',) + e.args[1:]
raise
This is indeed the only solution here that solves the problem in Python 3 without an ugly and confusing "During handling of the above exception, another exception occurred" message.
In case the re-raising line should be added to the stack trace, writing raise e instead of raise will do the trick.
I don't like all the given answers so far. They are still too verbose imho. In either code and message output.
All i want to have is the stacktrace pointing to the source exception, no exception stuff in between, so no creation of new exceptions, just re-raising the original with all the relevant stack frame states in it, that led there.
Steve Howard gave a nice answer which i want to extend, no, reduce ... to python 3 only.
except Exception as e:
e.args = ("Some failure state", *e.args)
raise
The only new thing is the parameter expansion/unpacking which makes it small and easy enough for me to use.
Try it:
foo = None
try:
try:
state = "bar"
foo.append(state)
except Exception as e:
e.args = ("Appending '"+state+"' failed", *e.args)
raise
print(foo[0]) # would raise too
except Exception as e:
e.args = ("print(foo) failed: " + str(foo), *e.args)
raise
This will give you:
Traceback (most recent call last):
File "test.py", line 6, in <module>
foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")
A simple pretty-print could be something like
print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))
With PEP 678 (Python 3.11) adding notes to exceptions is natively supported:
try:
raise TypeError('bad type')
except Exception as e:
e.add_note('Add some information')
raise
Rendered as:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: bad type
Add some information
I was hopping it could replace Steve Howard solution, Unfortunately, it does not give user any control on how to format the final exception (e.g. can't add a note before the exception like: 'Error in fn: {original_exc}')
If you want more control on the traceback, you can use https://github.com/google/etils:
from etils import epy
with epy.maybe_reraise('Error in fn: '):
fn()
Or:
try:
fn()
except Exception as e:
epy.reraise(e, suffix='. Did you mean y ?')
One handy approach that I used is to use class attribute as storage for details, as class attribute is accessible both from class object and class instance:
class CustomError(Exception):
def __init__(self, details: Dict):
self.details = details
Then in your code:
raise CustomError({'data': 5})
And when catching an error:
except CustomError as e:
# Do whatever you want with the exception instance
print(e.details)
I will provide a snippet of code that I use often whenever I want to add extra info to an exception. I works both in Python 2.7 and 3.6.
import sys
import traceback
try:
a = 1
b = 1j
# The line below raises an exception because
# we cannot compare int to complex.
m = max(a, b)
except Exception as ex:
# I create my informational message for debugging:
msg = "a=%r, b=%r" % (a, b)
# Gather the information from the original exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Format the original exception for a nice printout:
traceback_string = ''.join(traceback.format_exception(
exc_type, exc_value, exc_traceback))
# Re-raise a new exception of the same class as the original one,
# using my custom message and the original traceback:
raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))
The code above results in the following output:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
14 raise type(ex)(
15 "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16 (msg, traceback_string))
TypeError: a=1, b=1j
ORIGINAL TRACEBACK:
Traceback (most recent call last):
File "<ipython-input-6-09b74752c60d>", line 7, in <module>
m = max(a, b) # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers
I know this deviates a little from the example provided in the question, but nevertheless I hope someone finds it useful.
You can define your own exception that inherits from another and create it's own constructor to set value.
For example:
class MyError(Exception):
def __init__(self, value):
self.value = value
Exception.__init__(self)
def __str__(self):
return repr(self.value)
Unlike previous answers, this works in the face of exceptions with really bad __str__.
It does modify the type however, in order to factor out unhelpful __str__ implementations.
I'd still like to find an additional improvement that doesn't modify the type.
from contextlib import contextmanager
#contextmanager
def helpful_info():
try:
yield
except Exception as e:
class CloneException(Exception): pass
CloneException.__name__ = type(e).__name__
CloneException.__module___ = type(e).__module__
helpful_message = '%s\n\nhelpful info!' % e
import sys
raise CloneException, helpful_message, sys.exc_traceback
class BadException(Exception):
def __str__(self):
return 'wat.'
with helpful_info():
raise BadException('fooooo')
The original traceback and type (name) are preserved.
Traceback (most recent call last):
File "re_raise.py", line 20, in <module>
raise BadException('fooooo')
File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
self.gen.throw(type, value, traceback)
File "re_raise.py", line 5, in helpful_info
yield
File "re_raise.py", line 20, in <module>
raise BadException('fooooo')
__main__.BadException: wat.
helpful info!
Here's what I use for personal projects (I'm sure there's ample reason not to do this in production code):
try:
#something hazardous
except Exception as e:
insightful_message = "shouldn't have done that"
amended_args = tuple([f'{e.args[0]}\n{insightful_message}', *e.args[1:]])
e.args = amended_args
raise
The code (1) intercepts the error; (2) creates a copy of the error's .args property, which is a tuple that is assumed to include an error message at index 0, achieved using a list comprehension; (3) appends a line break and a custom message to the error message; (4) appends any additional items of .args to the copy using
unpacking; (5) converts the copy to a tuple; and finally (6) replaces .args with the amended copy.
Most of these operations are to circumvent the immutability of the .args tuple.
This is my implementation, to use it as a context manager and optionally add extra message to exception:
from typing import Optional, Type
from types import TracebackType
class _addInfoOnException():
def __init__(self, info: str = ""):
self.info = info
def __enter__(self):
return
def __exit__(self,
exc_type: Optional[Type[BaseException]],
exc_val: BaseException, # Optional, but not None if exc_type is not None
exc_tb: TracebackType): # Optional, but not None if exc_type is not None
if exc_type:
if self.info:
newMsg = f"{self.info}\n\tLow level error: "
if len(exc_val.args) == 0:
exc_val.args = (self.info, )
elif len(exc_val.args) == 1:
exc_val.args = (f"{newMsg}{exc_val.args[0]}", )
elif len(exc_val.args) > 0:
exc_val.args = (f"{newMsg}{exc_val.args[0]}", exc_val.args[1:])
raise
Usage:
def main():
try:
raise Exception("Example exception msg")
except Exception:
traceback.print_exc()
print("\n\n")
try:
with _addInfoOnException():
raise Exception("Example exception msg, no extra info")
except Exception:
traceback.print_exc()
print("\n\n")
try:
with _addInfoOnException("Some extra info!"):
raise Exception("Example exception msg")
except Exception:
traceback.print_exc()
print("\n\n")
if __name__ == "__main__":
main()
This would resolve in such traceback:
Traceback (most recent call last):
File "<...>\VSCodeDevWorkspace\testis.py", line 40, in main
raise Exception("Example exception msg")
Exception: Example exception msg
Traceback (most recent call last):
File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
raise Exception("Example exception msg, no extra info")
File "<...>\VSCodeDevWorkspace\testis.py", line 47, in main
raise Exception("Example exception msg, no extra info")
Exception: Example exception msg, no extra info
Traceback (most recent call last):
File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
raise Exception("Example exception msg")
File "<...>\VSCodeDevWorkspace\testis.py", line 54, in main
raise Exception("Example exception msg")
Exception: Some extra info!
Low level error: Example exception msg
I use in my codes:
try:
a=1
b=0
c=a/b
except:
raise Exception(f"can't divide {a} with {b}")
output:
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11708/1469673756.py in <module>
3 b=0
----> 4 c=a/b
5
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Exception Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_11708/1469673756.py in <module>
5
6 except Exception:
----> 7 raise Exception(f"can't divide {a} with {b}")
Exception: can't divide 1 with 0
Maybe
except Exception as e:
raise IOError(e.message + 'happens at %s'%arg1)