Printing passed bytes from Exception - python

This seems to be the code for the poplib.error_proto.
class error_proto(Exception): pass
It just passes the bytes from the POP response in the exception. What I would like to do is catch any exception, take those bytes, use .decode('ascii') on them, and print them as a string. I've written my own test setup like so:
class B(Exception): pass
def bex(): raise B(b'Problem')
try:
bex()
except B as err:
print(err.decode('ascii'))
I've tried replacing the last line with:
b = bytes(err)
print(b.decode('ascii'))
But to no avail. Is this possible and if so, how would I implement this?
UPDATE: Though, as falsetru points out, the documentation says results are returned as strings, this is not the case:
>>> p = poplib.POP3('mail.site.com')
>>> p.user('skillian#site.com')
b'+OK '
>>> p.pass_('badpassword')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python33\lib\poplib.py", line 201, in pass_
return self._shortcmd('PASS %s' % pswd)
File "C:\Python33\lib\poplib.py", line 164, in _shortcmd
return self._getresp()
File "C:\Python33\lib\poplib.py", line 140, in _getresp
raise error_proto(resp)
poplib.error_proto: b'-ERR authorization failed Check your server settings.'
>>>

According to poplib.error_proto documentation:
Exception raised on any errors from this module (errors from socket module are not caught). The reason for the exception is passed to the constructor as a string.
So, you don't need to decode it.
UPDATE It seems like the documentation does not match the actual implementation.
You can access the arguments passed to the exception constructor using args attribute.
p = poplib.POP3('mail.site.com')
try:
p.user('skillian#site.com')
p.pass_('badpassword')
except poplib.error_proto as e:
print(e.args[0].decode('ascii')) # `'ascii'` is not necessary.

Related

Tornado returning stacktrace on 500 server error [duplicate]

I want to catch and log exceptions without exiting, e.g.,
try:
do_stuff()
except Exception as err:
print(Exception, err)
# I want to print the entire traceback here,
# not just the exception name and details
I want to print the exact same output that is printed when the exception is raised without the try/except intercepting the exception, and I do not want it to exit my program.
traceback.format_exc() or sys.exc_info() will yield more info if that's what you want.
import traceback
import sys
try:
do_stuff()
except Exception:
print(traceback.format_exc())
# or
print(sys.exc_info()[2])
Some other answer have already pointed out the traceback module.
Please notice that with print_exc, in some corner cases, you will not obtain what you would expect. In Python 2.x:
import traceback
try:
raise TypeError("Oups!")
except Exception, err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_exc()
...will display the traceback of the last exception:
Traceback (most recent call last):
File "e.py", line 7, in <module>
raise TypeError("Again !?!")
TypeError: Again !?!
If you really need to access the original traceback one solution is to cache the exception infos as returned from exc_info in a local variable and display it using print_exception:
import traceback
import sys
try:
raise TypeError("Oups!")
except Exception, err:
try:
exc_info = sys.exc_info()
# do you usefull stuff here
# (potentially raising an exception)
try:
raise TypeError("Again !?!")
except:
pass
# end of useful stuff
finally:
# Display the *original* exception
traceback.print_exception(*exc_info)
del exc_info
Producing:
Traceback (most recent call last):
File "t.py", line 6, in <module>
raise TypeError("Oups!")
TypeError: Oups!
Few pitfalls with this though:
From the doc of sys_info:
Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement)
but, from the same doc:
Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.
On the other hand, by allowing you to access the traceback associated with an exception, Python 3 produce a less surprising result:
import traceback
try:
raise TypeError("Oups!")
except Exception as err:
try:
raise TypeError("Again !?!")
except:
pass
traceback.print_tb(err.__traceback__)
... will display:
File "e3.py", line 4, in <module>
raise TypeError("Oups!")
If you're debugging and just want to see the current stack trace, you can simply call:
traceback.print_stack()
There's no need to manually raise an exception just to catch it again.
How to print the full traceback without halting the program?
When you don't want to halt your program on an error, you need to handle that error with a try/except:
try:
do_something_that_might_error()
except Exception as error:
handle_the_error(error)
To extract the full traceback, we'll use the traceback module from the standard library:
import traceback
And to create a decently complicated stacktrace to demonstrate that we get the full stacktrace:
def raise_error():
raise RuntimeError('something bad happened!')
def do_something_that_might_error():
raise_error()
Printing
To print the full traceback, use the traceback.print_exc method:
try:
do_something_that_might_error()
except Exception as error:
traceback.print_exc()
Which prints:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Better than printing, logging:
However, a best practice is to have a logger set up for your module. It will know the name of the module and be able to change levels (among other attributes, such as handlers)
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
In which case, you'll want the logger.exception function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.exception(error)
Which logs:
ERROR:__main__:something bad happened!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Or perhaps you just want the string, in which case, you'll want the traceback.format_exc function instead:
try:
do_something_that_might_error()
except Exception as error:
logger.debug(traceback.format_exc())
Which logs:
DEBUG:__main__:Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Conclusion
And for all three options, we see we get the same output as when we have an error:
>>> do_something_that_might_error()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in do_something_that_might_error
File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Which to use
Performance concerns aren't important here as IO usually dominates. I'd prefer, since it does precisely what's being requested in a forward compatible way:
logger.exception(error)
Logging levels and outputs can be adjusted, making it easy to turn off without touching the code. And usually doing what's directly needed is the most efficient way to do it.
First, do not use prints for logging, there is a stable, proven and well-thought out stdlib module to do that: logging. You definitely should use it instead.
Second, do not be tempted to do a mess with unrelated tools when there is a native and simple approach. Here it is:
log = logging.getLogger(__name__)
try:
call_code_that_fails()
except MyError:
log.exception('Any extra info you want to see in your logs')
That's it. You are done now.
Explanation for anyone who is interested in how things work under the hood
What log.exception is actually doing is just a call to log.error (that is, log event with level ERROR) and print traceback then.
Why is it better?
Well, here are some considerations:
it is just right;
it is straightforward;
it is simple.
Why should nobody use traceback or call logger with exc_info=True or get their hands dirty with sys.exc_info?
Well, just because! They all exist for different purposes. For example, traceback.print_exc's output is a little bit different from tracebacks produced by the interpreter itself. If you use it, you will confuse anyone who reads your logs, they will be banging their heads against them.
Passing exc_info=True to log calls is just inappropriate. But, it is useful when catching recoverable errors and you want to log them (using, e.g INFO level) with tracebacks as well, because log.exception produces logs of only one level - ERROR.
And you definitely should avoid messing with sys.exc_info as much as you can. It's just not a public interface, it's an internal one - you can use it if you definitely know what you are doing. It is not intended for just printing exceptions.
traceback.format_exception(exception_object)
If you only have the exception object, you can get the traceback as a string from any point of the code in Python 3 with:
import traceback
''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
Full example:
#!/usr/bin/env python3
import traceback
def f():
g()
def g():
raise Exception('asdf')
try:
g()
except Exception as e:
exc_obj = e
tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)
Output:
Traceback (most recent call last):
File "./main.py", line 12, in <module>
g()
File "./main.py", line 9, in g
raise Exception('asdf')
Exception: asdf
Documentation: https://docs.python.org/3.9/library/traceback.html#traceback.format_exception
See also: Extract traceback info from an exception object
Tested in Python 3.9
In addition to Aaron Hall's answer, if you are logging, but don't want to use logging.exception() (since it logs at the ERROR level), you can use a lower level and pass exc_info=True. e.g.
try:
do_something_that_might_error()
except Exception:
logging.info('General exception noted.', exc_info=True)
I don't see this mentioned in any of the other answers. If you're passing around an Exception object for whatever reason...
In Python 3.5+ you can get a trace from an Exception object using traceback.TracebackException.from_exception(). For example:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
try:
stack_lvl_3()
except Exception as e:
# raise
return e
def stack_lvl_1():
e = stack_lvl_2()
return e
e = stack_lvl_1()
tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))
However, the above code results in:
Traceback (most recent call last):
File "exc.py", line 10, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')
This is just two levels of the stack, as opposed to what would have been printed on screen had the exception been raised in stack_lvl_2() and not intercepted (uncomment the # raise line).
As I understand it, that's because an exception records only the current level of the stack when it is raised, stack_lvl_3() in this case. As it's passed back up through the stack, more levels are being added to its __traceback__. But we intercepted it in stack_lvl_2(), meaning all it got to record was levels 3 and 2. To get the full trace as printed on stdout we'd have to catch it at the highest (lowest?) level:
import traceback
def stack_lvl_3():
raise Exception('a1', 'b2', 'c3')
def stack_lvl_2():
stack_lvl_3()
def stack_lvl_1():
stack_lvl_2()
try:
stack_lvl_1()
except Exception as exc:
tb = traceback.TracebackException.from_exception(exc)
print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))
Which results in:
Handled at stack lvl 0
File "exc.py", line 17, in <module>
stack_lvl_1()
File "exc.py", line 13, in stack_lvl_1
stack_lvl_2()
File "exc.py", line 9, in stack_lvl_2
stack_lvl_3()
File "exc.py", line 5, in stack_lvl_3
raise Exception('a1', 'b2', 'c3')
Notice that the stack print is different, the first and last lines are missing. Because it's a different format().
Intercepting the exception as far away from the point where it was raised as possible makes for simpler code while also giving more information.
In python3 (works in 3.9) we can define a function and can use that where ever we want to print the details.
import traceback
def get_traceback(e):
lines = traceback.format_exception(type(e), e, e.__traceback__)
return ''.join(lines)
try:
1/0
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
try:
spam(1,2)
except Exception as e:
print('------Start--------')
print(get_traceback(e))
print('------End--------')
The output would be like:
bash-3.2$ python3 /Users/soumyabratakole/PycharmProjects/pythonProject/main.py
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 26, in <module>
1/0
ZeroDivisionError: division by zero
------End--------
------Start--------
Traceback (most recent call last):
File "/Users/soumyabratakole/PycharmProjects/pythonProject/main.py", line 33, in <module>
spam(1,2)
NameError: name 'spam' is not defined
------End--------
If you have an Error object already, and you want to print the whole thing, you need to make this slightly awkward call:
import traceback
traceback.print_exception(type(err), err, err.__traceback__)
That's right, print_exception takes three positional arguments: The type of the exception, the actual exception object, and the exception's own internal traceback property.
In python 3.5 or later, the type(err) is optional... but it's a positional argument, so you still have to explicitly pass None in its place.
traceback.print_exception(None, err, err.__traceback__)
I have no idea why all of this isn't just traceback.print_exception(err). Why you would ever want to print out an error, along with a traceback other than the one that belongs to that error, is beyond me.
To get the precise stack trace, as a string, that would have been raised if no try/except were there to step over it, simply place this in the except block that catches the offending exception.
desired_trace = traceback.format_exc(sys.exc_info())
Here's how to use it (assuming flaky_func is defined, and log calls your favorite logging system):
import traceback
import sys
try:
flaky_func()
except KeyboardInterrupt:
raise
except Exception:
desired_trace = traceback.format_exc(sys.exc_info())
log(desired_trace)
It's a good idea to catch and re-raise KeyboardInterrupts, so that you can still kill the program using Ctrl-C. Logging is outside the scope of the question, but a good option is logging. Documentation for the sys and traceback modules.
You will need to put the try/except inside the most innerloop where the error may occur, i.e.
for i in something:
for j in somethingelse:
for k in whatever:
try:
something_complex(i, j, k)
except Exception, e:
print e
try:
something_less_complex(i, j)
except Exception, e:
print e
... and so on
In other words, you will need to wrap statements that may fail in try/except as specific as possible, in the most inner-loop as possible.
A remark about this answer's comments: print(traceback.format_exc()) does a better job for me than traceback.print_exc(). With the latter, the hello is sometimes strangely "mixed" with the traceback text, like if both want to write to stdout or stderr at the same time, producing weird output (at least when building from inside a text editor and viewing the output in the "Build results" panel).
Traceback (most recent call last):
File "C:\Users\User\Desktop\test.py", line 7, in
hell do_stuff()
File "C:\Users\User\Desktop\test.py", line 4, in do_stuff
1/0
ZeroDivisionError: integer division or modulo by zero
o
[Finished in 0.1s]
So I use:
import traceback, sys
def do_stuff():
1/0
try:
do_stuff()
except Exception:
print(traceback.format_exc())
print('hello')
import io
import traceback
try:
call_code_that_fails()
except:
errors = io.StringIO()
traceback.print_exc(file=errors) # Instead of printing directly to stdout, the result can be further processed
contents = str(errors.getvalue())
print(contents)
errors.close()
You want the traceback module. It will let you print stack dumps like Python normally does. In particular, the print_last function will print the last exception and a stack trace.
python 3 solution
stacktrace_helper.py:
from linecache import getline
import sys
import traceback
def get_stack_trace():
exc_type, exc_value, exc_tb = sys.exc_info()
trace = traceback.format_stack()
trace = list(filter(lambda x: ("\\lib\\" not in x and "/lib/" not in x and "stacktrace_helper.py" not in x), trace))
ex_type = exc_type.__name__
ex_line = exc_tb.tb_lineno
ex_file = exc_tb.tb_frame.f_code.co_filename
ex_message = str(exc_value)
line_code = ""
try:
line_code = getline(ex_file, ex_line).strip()
except:
pass
trace.insert(
0, f'File "{ex_file}", line {ex_line}, line_code: {line_code} , ex: {ex_type} {ex_message}',
)
return trace
def get_stack_trace_str(msg: str = ""):
trace = list(get_stack_trace())
trace_str = "\n".join(list(map(str, trace)))
trace_str = msg + "\n" + trace_str
return trace_str
This is my solution to write the error in a log file and also on console:
import logging, sys
import traceback
logging.basicConfig(filename='error.log', level=logging.DEBUG)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
exc_info=(exc_type, exc_value, exc_traceback)
logging.critical("\nDate:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))
print("An error occured, check error.log to see the error details")
traceback.print_exception(*exc_info)
sys.excepthook = handle_exception
You could do:
try:
do_stuff()
except Exception, err:
print(Exception, err)
raise err

Modifying exception message without losing stack from raise

I've got a try/except clause that will either return or catch a KeyError as follows:
try:
return super().__new__(globals()[kls])
except KeyError:
raise
This will generate a stack trace when used improperly like so:
>>> g = Grid(cell='Circle')
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
g = Grid(cell='Circle')
File "<pyshell#1>", line 8, in __new__
return super().__new__(globals()[kls])
KeyError: 'SHPCircleGrid'
>>> g
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
g
NameError: name 'g' is not defined
And that is perfectly fine, however I want to "extend/modify" the message to explain to the user how to avoid this error again; i.e. go from:
KeyError: 'SHPCircleGrid'
to
KeyError: 'SHPCircleGrid'. Use 'Hex', 'Rect' or 'Tri' for cell keyword.
While maintaining the stack for the user. A generic print() in the catching portion sets g to NoneType which I would not like, so simply printing is not the way to handle this. Adding another raise KeyError('some message') prints two stacks (the "while handling exception another..." message) which is also undesired.
What is the appropriate way to handle this such that it could be extended to any additional keywords that the class instantiation may throw a KeyError for?
Can't you just achieve that by supplying your message to KeyError as such:
try:
return super().__new__(globals()[kls])
except KeyError as e:
key = "'{}'".format(*e.args)
base = "{}. Use 'Hex', 'Rect' or 'Tri' for cell keyword."
raise KeyError(base.format(key)) from None

Python Execnet and Exception Handling

I am using execnet to call a jython module from within a python script.
From the docs:
Note that exceptions from the remotely executing code will be reraised as channel.RemoteError exceptions containing a textual representation of the remote traceback.
Let's say my remote module could result in two different exceptions, and I would like to be able to handle each exception differently. How will I be able to handle this, given that both exceptions will instead throw a RemoteError exception that only contains a string of the traceback?
For example, this particular calling code:
#...
channel.send('bogus')
results in the following RemoteError, which just contains an attribute formatted that contains a string of the traceback:
RemoteError: Traceback (most recent call last):
File "<string>", line 1072, in executetask
File "<string>", line 1, in do_exec
File "<remote exec>", line 33, in <module>
IOError: Open failed for table: bogus, error: No such file or directory (2)
I cannot do a try ... except IOError:. I could do a try ... except RemoteError as ex: and parse the ex.formatted to see if it contains IOError, and then raise that instead, but this seems rather sloppy:
from execnet.gateway_base import RemoteError
try:
channel.send('bogus')
except RemoteError as ex:
if 'IOError' in ex.formatted:
raise IOError(ex.formatted[ex.formatted.find('IOError'): -1])
if 'ValueError' in ex.formatted:
raise ValueError(ex.formatted[ex.formatted.find('ValueError'): -1])
# otherwise, reraise the uncaptured error:
raise ex
An old question - I tried to answer it
import unittest
from execnet.gateway_base import RemoteError
import execnet
class Test(unittest.TestCase):
def RemoteErrorHandler(self,error):
e,t = error.formatted.splitlines()[-1].split(':')
raise getattr(__builtins__,e)(t)
def raising_receive(self, ch):
try:
return ch.receive()
except RemoteError as ex:
self.RemoteErrorHandler(ex)
def setUp(self):
self.gateway = execnet.makegateway()
def test_NameError(self):
ch = self.gateway.remote_exec("print o")
with self.assertRaises(NameError):
self.raising_receive(ch)
if __name__ == '__main__':
unittest.main()

Let an exception raise itself

Is it possible to let an exception raise itself, in its own class instance ?
Something like this:
class Error(Exception):
def __init__(self):
if some stuff:
pass
elif some other stuff:
# re-raise myself
raise Error()
I've tried to use raise self but it returns an error ; also, I don't see any attribute to the exception class that seems to be linked to this.
Edit
If I want to do this, it is because I've attached some special methods to my Exception class, which format the error message to be printed depending of what parameters are passed to the exception. I could have done the formatting job in a separate object, but as it is only used by the exception, it seemed to me natural to attach it to the exception class itself.
You can raise an exception in the __init__ of an exception type, just like you can do so in any type’s __init__. This however will prevent the object from being created—instead, the exception is thrown.
So your original raise Error() will not be called at all, and the exception object you wanted to create there is never created. Instead, you will just get an exception because the object you created (Error) couldn’t be created.
So you lose all the information about the actual raise which you wanted to execute.
As such, no, don’t do that.
In any way this also seems to be a very odd way to do have an exception type. The exception object itself should not be dependent on any outside factors, and some stuff does appear to be something magic here. What is it you are actually trying to solve here?
What might help understanding, would be writing your raise Error() like this:
e = Error()
raise e
So you first create an exception object, and only then you actually raise it. So inside the __init__ of that type, you cannot reraise it as it has never been raised yet. But there is no need to reraise it anyway, because you are still raising it afterwards. So again: What are you trying to do? Do you want the exception object itself to be capable of preventing the raise on certain conditions (hence the pass in your code)?
Update
it is because I've attached some special methods to my Exception class, which format the error message to be printed depending of what parameters are passed to the exception.
That does not justify raising an exception in the __init__ of the type at all. You can easily format your exception message all you want without having to raise the exception in the class itself. For example:
>>> class Error (Exception):
def __init__ (self, msg, rightAligned = False, spacedOut = False):
if spacedOut:
msg = ' '.join(msg)
if rightAligned:
msg = msg.rjust(72)
Exception.__init__(self, msg)
>>> raise Error('Random message')
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
raise Error('Random message')
Error: Random message
>>> raise Error('Random message', True)
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
raise Error('Random message', True)
Error: Random message
>>> raise Error('Random message', False, True)
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
raise Error('Random message', False, True)
Error: R a n d o m m e s s a g e
As you can see, I can change the exception message in whatever way I want without having to raise an exception in the class.
EDIT: This answer should not have been accepted. poke's answer address the heart of the problem and presents a correct solution.
A self-raising class seems to work fine for me:
>>> class Err(Exception):
... def __init__(self):
... Exception.__init__(self, 'gulp!')
... raise self
...
>>> raise Err()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __init__
__main__.Err: gulp!
I'm not a Python language export, so this might be some weird half-aborted class that doesn't behave itself very well. Caveat emptor.
The burning question: why would you do this? Is there some reason you don't want to use the standard Python idiom of explicitly raising the exception after constructing it?
This might solve your immediate error:
raise self.__class__
However then you have a potential for infinite recursion. what if "some other stuff" is always true?

Adding information to an exception?

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)

Categories

Resources