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

user202729
  • 3,358
  • 3
  • 25
  • 36
Evan Pu
  • 2,099
  • 5
  • 21
  • 36
  • Crosslink: special case of [python - When I catch an exception, how do I get the type, file, and line number? - Stack Overflow](https://stackoverflow.com/questions/1278705/when-i-catch-an-exception-how-do-i-get-the-type-file-and-line-number). For any future readers coming across this.empty()ing for how to display traceback in exec() code refer to [python - File "" traceback with line preview - Stack Overflow](https://stackoverflow.com/questions/47183305/file-string-traceback-with-line-preview). – user202729 May 20 '23 at 01:05
  • Looks like same question as [how to get the line number of an error from exec or execfile in Python - Stack Overflow](https://stackoverflow.com/questions/28836078/how-to-get-the-line-number-of-an-error-from-exec-or-execfile-in-python) – user202729 May 20 '23 at 01:28

3 Answers3

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)

JustMe
  • 710
  • 4
  • 16
  • what if my string is passed on from outside instead of being known at the time i wrote the script? – Evan Pu Feb 22 '16 at 21:58
  • 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. – JustMe Feb 22 '16 at 22:04
  • 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! – Evan Pu Feb 22 '16 at 22:29
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
Matt Williamson
  • 39,165
  • 10
  • 64
  • 72
  • exec would have defined a function in the local scope, which I then execute with "locals()[func_name](inp)" – Evan Pu Feb 22 '16 at 22:03
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.

Evan Pu
  • 2,099
  • 5
  • 21
  • 36