I have this line of Python code:
raise ValueError(f"Invalid input '{number}'")
When it raises an exception I get:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/snowcrash/Code/Python/mycode/mycode.py", line 8, in __init__
raise ValueError(f"Invalid input '{number}'")
ValueError: Invalid input 'a1b2c3'
however I'd prefer to get:
File "/home/snowcrash/Code/Python/mycode/mycode.py", line 8, in __init__
ValueError: Invalid input 'a1b2c3'
How do I achieve this?
sys.excepthook(type, value, traceback) is the function that does the printing when an exception is raised. You can write your own function (with a similar signature) and simply replace it with:
sys.excepthook = myfunction
You can do whatever you want in this function. You may log to files, or print some information about the state of the program when the exception occurred, for example. You may even arrange for different things to happen depending on the type of exception.
However if all you want to do is format your traceback message, lots of convenient stuff is already provided for you. You can dig into the traceback docs for the details, but here is an example that produces roughly the output you want:
import sys, traceback
def myhook(type, value, tb):
trace = traceback.format_tb(tb, limit=1)
trace = trace[0].split("\n")[0]
exc = traceback.format_exception_only(type, value)[0]
print(trace + "\n" + exc)
sys.excepthook = myhook
number = "a1b2c3"
raise ValueError(f"Invalid input {number}")
which produces an output of:
File "scratch/so.py", line 14, in <module>
ValueError: Invalid input a1b2c3
Note especially the use of traceback.format_tb with limit=1, to limit the stack trace output. There are a bunch of these functions in the traceback module.
Related
I've been trying to get a specific (and most likely useless) effect on the traceback when I raise an error.
I would like to hide the raise Exception("Message") of the traceback inside my function and instead have my trace indicate the function call that triggered the raise inside the function
def crash(something):
if something > 200:
raise ValueError("Value too large !")
if __name__ == '__main__':
crash(500)
Normally generates :
Traceback (most recent call last):
File "file.py", line 7, in <module>
crash(500)
File "file.py", line 3, in crash
raise ValueError("Value too large !")
ValueError: Value too large !
Instead I would like to have this :
Traceback (most recent call last):
File "file.py", line 7, in <module>
crash(500)
ValueError: Value too large !
I find it clearer for the user trying to use the module because it clearly shows where the problem is as otherwise he could potentially think the module itself is at fault
I tried to find solution that would "remove" the last call from the traceback but it always had "side effects" like showing part of the code trying to remove the last trace call INSIDE the traceback itself, making it even more confusing. Sometimes it would also repeat the the traceback over what I really want.
For example using this :
def crash():
frame = sys._getframe(1)
tb = types.TracebackType(None, frame, frame.f_lasti, frame.f_lineno)
raise ValueError("Wrong value").with_traceback(tb)
Prints this :
Traceback (most recent call last):
File "file.py", line 34, in <module>
crash()
File "file.py", line 9, in crash
raise ValueError("Wrong value").with_traceback(tb)
File "file.py", line 34, in <module>
crash()
ValueError: Wrong value
I also tried using another function to create the trace myself but ended up with a weird behavior
def crash():
raise exception_no_raise(ValueError("Wrong value"))
def exception_no_raise(exc: Exception):
tb = None
depth = 0
while True:
try:
sys._getframe(depth)
depth += 1
except ValueError:
break
# for i in range(depth-1, 1, -1):
# frame = sys._getframe(i)
# tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
# traceback.print_tb(tb)
# print(file=sys.stderr)
frame = sys._getframe(depth-1)
tb = types.TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
return exc.with_traceback(tb)
Prints :
Traceback (most recent call last):
File "file.py", line 32, in <module>
crash()
File "file.py", line 7, in crash
raise exception_no_raise(ValueError("Wrong value"))
File "file.py", line 32, in <module>
crash()
ValueError: Wrong value
Even tho when you use traceback.print_tb(tb) (before the return) you get the exact trace I want even tho using it to raise the exception doesn't print it :
File "file.py", line 34, in <module>
crash()
The last solution I found was to "strip" the last traceback from the tb.tb_next chain of traces after catching the exception and then re-raise it (but it showed the raise code anyway so ...)
I have difficulties to grasp how exactly traceback works and it seems that it changes drastically from version to version as I found code from Python 2 and 3 and also code the works in Python 3.8 and not before (related to the fact that you couldn't write to the tb.next of a trace before
Thanks for your help and clarifications !
So when I run this... the error is on this line bomb=pd.DataFrame(here,0) but the trace shows me a bunch of code from the pandas library to get to the error.
import traceback,sys
import pandas as pd
def error_handle(err_var,instance_name=None): #err_var list of variables, instance_name
print(traceback.format_exc())
a= sys._getframe(1).f_locals
for i in err_var: # selected var for instance
t= a[instance_name]
print i,"--->",getattr(t,i.split(".")[1])
here=['foo']
err_var = ['self.needthisone','self.constant2']
class test:
def __init__(self):
self.constant1 = 'hi1'
#self.constant2 = 'hi2'
#self.needthisone = ':)'
for i in err_var:
setattr(self, i.split('.')[1], None)
def other_function(self):
self.other_var=5
def testing(self):
self.other_function()
vars=[self.constant1,self.constant2]
try:
for i in vars:
bomb=pd.DataFrame(here,0)
except:
error_handle(err_var,'self')
t=test()
t.testing()
How do I suppress all that and have the error just look like this:
Traceback (most recent call last):
File "C:\Users\Jason\Google Drive\python\error_handling.py", line 34, in testing
bomb=pd.DataFrame(here,0)
TypeError: Index(...) must be called with a collection of some kind, 0 was passed
I just want what's relevant to me and the last line of code that I wrote which was bad.
This is the original:
Traceback (most recent call last):
File "C:\Users\Jason\Google Drive\python\error_handling.py", line 35, in testing
bomb=pd.DataFrame(here,0)
File "C:\Python27\lib\site-packages\pandas\core\frame.py", line 330, in __init__
copy=copy)
File "C:\Python27\lib\site-packages\pandas\core\frame.py", line 474, in _init_ndarray
index, columns = _get_axes(*values.shape)
File "C:\Python27\lib\site-packages\pandas\core\frame.py", line 436, in _get_axes
index = _ensure_index(index)
File "C:\Python27\lib\site-packages\pandas\core\indexes\base.py", line 3978, in _ensure_index
return Index(index_like)
File "C:\Python27\lib\site-packages\pandas\core\indexes\base.py", line 326, in __new__
cls._scalar_data_error(data)
File "C:\Python27\lib\site-packages\pandas\core\indexes\base.py", line 678, in _scalar_data_error
repr(data)))
TypeError: Index(...) must be called with a collection of some kind, 0 was passed
self.needthisone ---> None
self.constant2 ---> None
You can define how far back a traceback goes using the sys.traceback variable. If your code is only 3 levels deep, (a function in a class in a file), then you can define this appropriately with the code:
sys.tracebacklimit = 3
at the top of your file. BE CAREFUL WITH THIS: As you write more code, the portion that you've written will become deeper and deeper, and you may sometime soon find that an error is a result of something deeper in the traceback. As a general rule, I would avoid using the variable and just deal with a longer traceback for the time being.
Please, don't ever think about limiting the stack trace. It is very important.
Only at this moment, in this small example of yours, the error really is in your code.
But in an infinite other cases, an error could be triggered much deeper than that. It could be in the framework, or even out of the code whatsoever, like a configuration error, or it could be in the platform, like an Out of Memory error, etc.
The stack trace is there to help you. It lists all frames the compiler was executing, to give you all the info you need to understand what was going on.
I would highly encourage you not to limit your traceback output, because it is bad practice. You feel like there is too much info; but this is only because you already looked at it already and you know what error to look for.
In most cases, the problem may be hiding elsewhere. So there has to be a better way to achieve what you look for.
Why not wrap your function call in a try except clause and print the exception message? Take this scenario for example:
def f():
a = 0
i = 1
print i/a
def another_func():
print 'this is another func'
return f()
def higher_level_func():
print 'this is higher level'
return another_func()
if __name__ == '__main__':
try:
higher_level_func()
except Exception as e:
print 'caught the exception: {}-{}'.format(type(e)__name__, e.message)
When called, this is the output:
this is higher level
this is another func
caught the exception: ZeroDivisionError-integer division or modulo by zero
This prints only the relevant exception in your code, hiding any information about the traceback, but the traceback is still available and you can print it as well (just raise the exception from your except block).
Compared to this, if I remove the try except block:
this is higher level
this is another func
caught the exception: integer division or modulo by zero
Traceback (most recent call last):
File "test.py", line 17, in <module>
higher_level_func()
File "test.py", line 12, in higher_level_func
return another_func()
File "test.py", line 8, in another_func
return f()
File "test.py", line 4, in f
print i/a
ZeroDivisionError: integer division or modulo by zero
You better use this technique to capture the relevant exception, rather than limiting the tracebacks. If you want your program to stop, just add sys.exit(1) in the except block.
Having a raised exception I would like to jump into that frame. To explain better what I mean I wrote this mwe:
Assuming I have the following code:
from multiprocessing import Pool
import sys
# Setup debugger
def raiseDebugger(*args):
""" http://code.activestate.com/recipes/65287-automatically-start-the-
debugger-on-an-exception/ """
import traceback, pdb
traceback.print_exception(*args)
pdb.pm()
sys.excepthook = raiseDebugger
# Now start with the question
def faulty(i):
return 1 / i
with Pool() as pool:
pool.map(faulty, range(6))
which unsurprisingly leads to:
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 119, in worker
result = (True, func(*args, **kwds))
File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 44, in mapstar
return list(map(*args))
File "test2.py", line 19, in faulty
return 1 / i
ZeroDivisionError: division by zero
"""
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test2.py", line 23, in <module>
pool.map(faulty, range(6))
File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 260, in map
return self._map_async(func, iterable, mapstar, chunksize).get()
File "/home/bin/conda/lib/python3.5/multiprocessing/pool.py", line 608, in get
raise self._value
ZeroDivisionError: division by zero
> /home/bin/conda/lib/python3.5/multiprocessing/pool.py(608)get()
-> raise self._value
(Pdb)
Now to debug the problem I would like to "jump" into the frame which originally raised the exception (ZeroDivisionError).
The original exception is still available under self._value complete with self._value.__traceback__.
The call that pm (or post_mortem) calls is from the value field of sys.exc_info, and the default invocation of post_mortem is done on the __traceback__ of that value. However if you want to get to the underlying object, you want to access its __context__ instead. Given this code example:
import pdb
import sys
import traceback
def top():
value = 1
raise Exception('this always fails')
def bottom():
try:
top()
except Exception as bot_ex:
x = {}
return x['nothing']
try:
bottom()
except Exception as main_ex:
pdb.post_mortem()
Running the code. The main_ex would be analogous to your self._value.
> /tmp/foo.py(14)bottom()
-> return x['nothing']
(Pdb) main_ex
KeyError('nothing',)
(Pdb) pdb.post_mortem(main_ex.__traceback__)
> /tmp/foo.py(14)bottom()
-> return x['nothing']
Note we have a new pdb prompt at the same location, which is where the exception was originally raised. Let's try it with __context__ if we need to go further up:
(Pdb) c
(Pdb) pdb.post_mortem(main_ex.__context__.__traceback__)
> /tmp/foo.py(7)top()
-> raise Exception('this always fails')
If needed, keep repeating until you get to the target context/traceback desired.
Now for the multiprocessing case, which I wasn't aware would have made this much difference, as the question implies something general (How can I “jump” into stackframe from exception?), but it turns out the specifics in multiprocessing made all the difference.
In Python 3.4 a workaround was done to just show that traceback as a string; due to how much stuff a traceback actually has, communicating all that proved to be difficult as discussed in the issue 13831 on the Python tracker, so instead a hack was done to bring a __cause__ attribute into the current exception, but it is no full __traceback__ as it just has the string representation of that, as I had suspected.
Anyway this is what would have happened:
(Pdb) !import pdb
(Pdb) !self._value.__cause__
RemoteTraceback('\n"""\nTraceback (most recent call last):...',)
(Pdb) !type(self._value.__cause__)
<class 'multiprocessing.pool.RemoteTraceback'>
(Pdb) !self._value.__cause__.__traceback__
(Pdb) !self._value.__cause__.__context__
So this isn't actually possible until they figure out how to bring all those states across process boundaries.
In a python program I am making, I want it to only take integers, and if it gets a string say "There has been an error in the system." instead of murmering sensless information the user will not understand
Use a try-except block to capture the error and use the raise statement to say the error message of your choice:
try:
a = int(input())
except:
raise Exception('There has been an error in the system')
Using try, except and raise
Since ValueError inherits from the Exception class, the first parameter when creating a ValueError object is the message it prints:
try:
int("string") #the code that raises the error
except ValueError:
raise ValueError("Your custom message here.")
This prints:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: invalid literal for int() with base 10: 'string'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: Your custom message here.
If you don't want the previous error chain to print, put from None in the raise statement:
try:
int("string") #the code that raises the error
except ValueError:
raise ValueError("Your custom message here.") from None
This prints:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: Your custom message here.
I suggest you leave the chain because it gives more information, like what was inputted that raised the error. If you want to include the information from the original error in the custom message, use the error's attributes:
from traceback import format_tb
try:
int("string") #the code that raises the error
except ValueError as err:
raise ValueError("Custom message with traceback and original message\n" + format_tb(err.__traceback__)[0] + err.args[0] + "\nEnd of error message.") from None
This prints
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ValueError: Custom message with traceback and message
File "<stdin>", line 2, in <module>
invalid literal for int() with base 10: 'string'
End of error message.
Though this allows for customization of the error's print, the code is a little unpythonic.
Using assert
Because in the question you said you wanted to block all strings, you can use assert and isinstance():
obj = "a string" #the input you want to raise the error on
assert not isinstance(obj, str), "Your custom message here."
This prints:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: Your custom message here.
Though using assert looks clean, the error won't carry as much information because it would be a generic AssertionError. Raising a ValueError tells more information about what caused the error at a glance.
You need to use a try except block to catch the error - see the documentation. Then you could just print a message, and, if necessary, exit the program:
try:
value = int(input("Enter an integer: "))
except ValueError:
print("There has been an error in the system.")
input() # To let the user see the error message
# if you want to then exit the program
import sys
sys.exit(1)
If you want to make an error, you use raise. Here is an example:
raise SyntaxError('MUHAHA THIS IS A ERROR')
If you do not want to add another indentation level by using a try-except block, you can change the handling of all errors by adding the following to the beginning of your code:
import sys
def my_except_hook(exctype, value, traceback):
print('There has been an error in the system')
sys.excepthook = my_except_hook
In case of an error, only your specified error message is printed out. In addition, this prevents the stack trace from being displayed.
Use raise Exception('There has been an error in the system') may be useful for most cases but you may also need create meaningful errors for specific systems.
Eg.
class ExpiredTaxIdException(Exception):
def __init__(self):
Exception.__init__(self, 'Tax ID expired')
Then you can call it on your code, sample:
from your_execeptions_file import ExpiredTaxIdException
class ClientsController:
def is_profile_valid(client):
if client.tax_id.is_valid == False:
raise ExpiredTaxIdException()
return True
# eg. you call it on your API on another place of your code that can't accept invalid tax_id
try:
ClientsController().verify_client(client)
except ExpiredTaxIdException as e:
return {'error': str(e)}
>>> { "error": "Tax ID expired" }
You can try this..
import ctypes
ctypes.windll.user32.MessageBoxW(None, u"CUSTOM MESSAGE", u"TITLE BAR", 0)
Consider this try/except block I use for checking error message stored in e.
Try/Catch to get the e
queryString = "SELECT * FROM benchmark WHERE NOC = 2"
try:
res = db.query(queryString)
except SQLiteError, e:
# `e` has the error info
print `e`
The e object here contains nothing more than the above string. When python reports an unhandled error, however, it shows a pretty detailed info as below:
Traceback (most recent call last):
File "fool.py", line 1, in
open("abc.zyz", "r")
IOError: [Errno 2] No such file or directory: 'abc.zyz'
My question is, how can I get the information such as above (the file and the line number etc.)? Or, if e contains this info, how is it stored inside it?
This will show the trace to the error.
import traceback
try:
res = db.query(queryString)
except SQLiteError, e:
# `e` has the error info
print `e`
for tb in traceback.format_tb(sys.exc_info()[2]):
print tb
Like the first 2 answers, use traceback. Here is a more complete example.
import traceback
def foo():
raise RuntimeError('we have a problem')
try:
foo()
except:
traceback.print_exc()
When you run it, you'll see
Traceback (most recent call last):
File "C:\0\tmp\x.py", line 6, in <module>
foo()
File "C:\0\tmp\x.py", line 3, in foo
raise RuntimeError('we have a problem')
RuntimeError: we have a problem
See the traceback library.
If you want to just pass errors up the chain instead of modifying them, you can just use raise within an except block, which will then act like the except block isn't there (aside from any conditional logic/side effects you may have done before the raise).