How can I get the line number of a traceback programmatically when the code is exec() from a string?
Asked Answered
F

3

6

I have a function which is stored in a string, which looks something like this:

func_str = "def <func_name> ..."

I am evaluating it using "exec" and using it on an input as follows:

exec func_str in locals()
locals()[func_name](inp)

Now this function might have an exception, and I would like to know which line caused it in the string. Running it in the interpreter gives me an error message which is exactly what I want:

  File "<string>", line 6, in <func_name>
TypeError: can only concatenate tuple (not "int") to tuple

This tells me the 6th line in my string is causing the problem.

Is there some way of capturing this programmatically? I've looked at similar solutions but they don't address the exception coming from a string which was executed in the local scope. When attempting to use the traceback module I only got the line-number for the outer function that invoked the exec.

thanks

Forsberg answered 22/2, 2016 at 21:27 Comment(2)
Crosslink: special case of python - When I catch an exception, how do I get the type, file, and line number? - Stack Overflow. For any future readers coming across this.empty()ing for how to display traceback in exec() code refer to python - File "<string>" traceback with line preview - Stack Overflow.Surveillance
Looks like same question as how to get the line number of an error from exec or execfile in Python - Stack OverflowSurveillance
L
5

Well, this feels filthy and disgusting, but here You go.

sys.exc_info()[2].tb_next.tb_lineno + frameinfo.lineno

Lineno MUST be directly line above Your stringized code to eval, or if code starts at the begining of script - obviously it is not necessary.

import sys
from inspect import currentframe, getframeinfo

frameinfo = getframeinfo(currentframe())
func_str = """
def func_name(param):
  d = []
  u = 1
  pass
  a = ''
  pass
  print a + param
  print "hi"
  print "ho"
    """
exec func_str in locals()
inp = 1
try:
  locals()["func_name"](inp)
except Exception as e:
  print "Fails at:", sys.exc_info()[2].tb_next.tb_lineno + frameinfo.lineno
  print "Inside:", len(func_str.split("\n")) - frameinfo.lineno

output

Fails at: 12
Inside: 7

if You wanted "lineno" for this stringized source only, then

len(func_str.split("\n") - frameinfo.lineno

I don't know have You decided on this architecture on Your own or were forced to it, but I feel sorry :)

Edit:

If You receive string remotely

import sys
from inspect import currentframe, getframeinfo


some_item = "frameinfo = getframeinfo(currentframe())"

pass
random_items_here = 1

func_str = """
line_no = 2
lineno = 3
a_number = 0
def func_name(param):
  d = []
  u = 1
  pass
  a = ''
  pass
  print a + param
  print "hi"
  print "ho"
    """
exec some_item + "\n" + func_str in locals()
inp = 1
try:
  locals()["func_name"](inp)
except Exception as e:
  print "Fails at:", sys.exc_info()[2].tb_lineno
  print "Inside:", len(func_str.split("\n")) - 2 - frameinfo.lineno

out:

Fails at: 27
Inside: 11

but this appears to be failing on excess of new lines at the end (so You'd need to strip() func_str at least)

Librettist answered 22/2, 2016 at 21:53 Comment(3)
what if my string is passed on from outside instead of being known at the time i wrote the script?Forsberg
hmm, interesting, like sent via socket? Then You could let the craziness roll :) and append frameinfo = getframeinfo(currentframe()) before string You are about to execute.Librettist
well think of it as a remote call, someone send you a string to execute. Anyways I think i solved it with tb_next to go to the next layer. thanks for spending time answering!Forsberg
B
1

I think you'll want to use eval in this case. exec doesn't return anything:

>>> import traceback
>>> try: eval("1/0")
... except: print "Got exception:", traceback.format_exc()
...
Got exception: Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Barytone answered 22/2, 2016 at 21:43 Comment(1)
exec would have defined a function in the local scope, which I then execute with "locals()[func_name](inp)"Forsberg
F
0

Thanks to the answers, they were very helpful.

I think what I was missing was essentially a method to "go inside" the traceback stacks, as I was stopping on the outer exceptions instead of going to the absolute "root" of the failure

this did the trick for me:

def go_deeper(deeep):
  if deeep.tb_next == None:
    return deeep.tb_lineno
  else:
    return go_deeper(deeep.tb_next)

This will go to the most deep layer for the cause of the exception which is essentially all I needed.

Forsberg answered 22/2, 2016 at 22:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.