Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I find out which index is out of range?

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.

like image 493
David Board Avatar asked Sep 14 '18 12:09

David Board


3 Answers

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
like image 130
blhsing Avatar answered Oct 25 '22 23:10

blhsing


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.

like image 27
Thierry Lathuille Avatar answered Oct 25 '22 21:10

Thierry Lathuille


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.

like image 35
Olivier Melançon Avatar answered Oct 25 '22 23:10

Olivier Melançon