Reputation: 422
I have a script that reads in from a file of records that is checking for incorrect data. They might each throw the same exception and they exist on the same line. Is there a way to identify which field threw the exception without having to break it up into multiple lines?
Toy example here:
a = [1]
b = [2]
c = [] # Oh no, imagine something happened, like some data entry error
i = 0
try:
z = a[i] + b[i] + c[i]
except IndexError, e:
print "Data is missing! %s" % (str(e))
The problem is that if there's an exception, the user doesn't know if it's a, b, or c that has data missing.
I suppose I could write it as:
def check_data(data, index, message):
try:
return data[index]
except IndexError, e:
print "%s is missing." % (message)
raise e
a = [1]
b = [2]
c = []
i = 0
try:
z = check_data(a, i, "a") + check_data(b, i, "b") + check_data(c, i, "c")
except TypeError, e:
print "Error! We're done."
But that can be pretty tedious.
What other ways can I use to handle this situation to validate each field in exception blocks, if any exist?
Example adapted from reality below:
class Fork:
def __init__(self, index, fork_name, fork_goal, fork_success):
# In reality, we would do stuff here.
pass
forks = []
# In reality, we'd be reading these in and not all of the entries might exist.
fork_names = ["MatrixSpoon", "Spoon", "Spork"]
fork_goals = ["Bend", "Drink soup", "Drink soup but also spear food"]
fork_success = ["Yes!", "Yes!"]
try:
for i in range(0, len(fork_names)):
forks.append(Fork(i + 1, fork_names[i], fork_goals[i], fork_success[i]))
except IndexError, e:
print "There was a problem reading the forks! %s" % (e)
print "The field that is missing is: %s" % ("?")
Upvotes: 4
Views: 109
Reputation: 69051
You could move your error checking into the Fork
class and use itertools.izip_longest
to make sure /something/ (really None
) is passed in if one data stream runs short:
class Fork:
def __init__(self, index, fork_name, fork_goal, fork_success):
# first, check parameters
for name, value in (
('fork_name', fork_name),
('fork_goal', fork_goal),
('fork_success', fork_success)
):
if value is None:
raise ValueError('%s not specified' % name)
# rest of code
forks = []
# In reality, we'd be reading these in and not all of the entries might exist.
fork_names = ["MatrixSpoon", "Spoon", "Spork"]
fork_goals = ["Bend", "Drink soup", "Drink soup but also spear food"]
fork_success = ["Yes!", "Yes!"]
and then change your loop like so:
for name, goal, sucess in izip_longest(fork_names, fork_goals, fork_success):
forks.append(Fork(names, goal, success))
Now, you'll get an error clearly detailing which data element was missing. If your missing element looks more like ''
than nothing, you could change the test in __init__
from if value is None
to if not value
.
Upvotes: 1
Reputation: 28868
When you catch or the exception you still have the information on what caused the exception, for example:
c_1 = None
try:
c_1 = c[i]
except IndexError, e:
print "c is missing."
raise e # here you still have e and i
So you could do something like that:
try:
a = a_1[i]
except IndexError, e:
raise Exception(e.message+'the violation is because of '+str(i))
If you are interested in knowing what caused the violation, e.g. which list is two short, you can simply hard code the variables:
try:
for i in range(0, len(fork_names)):
forks.append(Fork(i + 1, fork_names[i], fork_goals[i], fork_success[i]))
except IndexError, e:
print "There was a problem reading the forks! %s" % (e)
print "There are fork_names with size %s " % len(fork_names)
print "There are fork_goals with size %s " % len(fork_goals)
print "There are fork_success with size %s " % len(fork_success)
print "You tried accessing index %d" % (i+1)
OK, I admit seems a lot of work! But it's worth it, because you have to think about your input and expect out put (TDD if you want...). But this is still quite lame, what if you don't know how a method called? Sometimes you will see this:
def some_function(arg1, arg2, *args, **kwrds)
pass
So, you can really hard code the stuff in the exceptions, for this case you can print the stack information, with sys.exc_info
:
try:
for i in range(0, len(fork_names)):
forks.append(Fork(i + 1, fork_names[i], fork_goals[i], fork_success[i]))
except IndexError, e:
type, value, traceback = sys.exc_info()
for k, v in traceback.tb_frame.f_locals.items():
if isinstance(k, (list,tuple)):
print k, " length ", len(k)
else:
print k, v
The above will output
Fork __main__.Fork
traceback <traceback object at 0x7fe51c7ea998>
e list index out of range
__builtins__ <module '__builtin__' (built-in)>
__file__ teststo.py
fork_names ['MatrixSpoon', 'Spoon', 'Spork']
value list index out of range
__package__ None
sys <module 'sys' (built-in)>
i 2
fork_success ['Yes!', 'Yes!']
__name__ __main__
forks [<__main__.Fork instance at 0x7fe51c7ea908>, <__main__.Fork instance at 0x7fe51c7ea950>]
fork_goals ['Bend', 'Drink soup', 'Drink soup but also spear food']
type <type 'exceptions.IndexError'>
__doc__ None
So, after examining the above trace you can figure out which list is too short. Im admitting, this is like shooting a debugger. So if you want, here is how to shoot a debugger:
try:
some_thing_that_fails()
except Exception:
import pdb; pdb.set_trace()
# if you want a better debugger that supports autocomplete and tab pressing
# to explore objects you should you use ipdb
# import ipdb; ipdb.set_trace()
for i in range(0, len(fork_names))
This is not really Pythonic. Instead you could use:
for idx, item enumerate(fork_names):
forks.append(Fork(idx + 1, fork_names[idx], fork_goals[idx], fork_success[idx]))
And like it was said in the comments, izip
and izip_longest
are worth looking into.
Upvotes: 1