In the event of an IndexError, is there a way to tell which object on a line is 'out of range'?
Consider this code:
a = [1,2,3]
b = [1,2,3]
x, y = get_values_from_somewhere()
try:
a[x] = b[y]
except IndexError as e:
....
In the event that x
or y
is too large and IndexError
gets caught, I would like to know which of a
or b
is out of range (so I can perform different actions in the except
block).
Clearly I could compare x
and y
to len(a)
and len(b)
respectively, but I am curious if there is another way of doing it using IndexError
.
A more robust approach would be to tap into the traceback object returned by sys.exc_info()
, extract the code indicated by the file name and line number from the frame, use ast.parse
to parse the line, subclass ast.NodeVisitor
to find all the Subscript
nodes, unparse (with astunparse
) and eval the nodes with the frame's global and local variables to see which of the nodes causes exception, and print the offending expression:
import sys
import linecache
import ast
import astunparse
def find_index_error():
tb = sys.exc_info()[2]
frame = tb.tb_frame
lineno = tb.tb_lineno
filename = frame.f_code.co_filename
line = linecache.getline(filename, lineno, frame.f_globals)
class find_index_error_node(ast.NodeVisitor):
def visit_Subscript(self, node):
expr = astunparse.unparse(node).strip()
try:
eval(expr, frame.f_globals, frame.f_locals)
except IndexError:
print("%s causes IndexError" % expr)
find_index_error_node().visit(ast.parse(line.strip(), filename))
a = [1,2,3]
b = [1,2,3]
x, y = 1, 2
def f():
return 3
try:
a[f() - 1] = b[f() + y] + a[x + 1] # can you guess which of them causes IndexError?
except IndexError:
find_index_error()
This outputs:
b[(f() + y)] causes IndexError
There is a way, but I wouldn't consider it as very robust. There is a subtle difference in the error messages:
a = [1,2,3]
b = [1,2,3]
a[2] = b[3]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-69-8e0d280b609d> in <module>()
2 b = [1,2,3]
3
----> 4 a[2] = b[3]
IndexError: list index out of range
But if the error is on the left hand side:
a[3] = b[2]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-68-9d66e07bc70d> in <module>()
2 b = [1,2,3]
3
----> 4 a[3] = b[2]
IndexError: list assignment index out of range
Note the 'assignment' in the message.
So, you could do something like:
a = [1,2,3]
b = [1,2,3]
try:
a[3] = b[2]
except IndexError as e:
message = e.args[0]
if 'assignment' in message:
print("Error on left hand side")
else:
print("Error on right hand side")
Output:
# Error on left hand side
Again, I wouldn't trust it too much, it would fail if the message changes in another version of Python.
I had a look at these parts of the source code, these different messages are really the only difference between the two errors.
The IndexError
exception does not store information about what raised the exception. Its only data is an error message. You have to craft your code to do so.
a = [1,2,3]
b = [1,2,3]
x, y = get_values_from_somewhere()
try:
value = b[y]
except IndexError as e:
...
try:
a[x] = value
except IndexError as e:
...
I want to add I tried to recover the culprit through inspect.frame
and was unable to do so. Thus I suspect there really is no robust way.
Finally, note that this is specific to IndexError
as other exceptions may contain the needed information to infer what caused them. By example a KeyError
contains the key that raised it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With