Search code examples
pythonexceptiontraceback

Python multi-line expressions and stack trace


We have a simple AssertTrue function used in our python project and I wanted to modify the output it provides to print the code statement from which it was called. The code looks something like this:

1 import traceback
2
3 def AssertTrue(expr, reason=None):
4     print traceback.format_stack()[-2]
5
6 AssertTrue(1 == 2,
7         reason='One is not equal to two')

The output:

File "/tmp/fisken.py", line 7, in <module>
  reason='One is not equal to two')

I'm wondering why traceback.format_stack only gives me the code on line 7. The statement starts on line 6 and the expression I would like to see in the output is also on that same line. Doesn't traceback handle multi-line function calls?

(Never mind that there are better ways to do AssertTrue(...). I'm just wondering why traceback.format_stack (and .extract_stack) does not behave as I expected it to)


Solution

  • Doesn't traceback handle multi-line function calls?

    Many functions are tens or even (horrors) hundreds of lines long. If traceback did print the whole function, then stack traces would become incomprehensibly long. So I guess what you are seeing is an attempt to keep things clean and minimal.

    I have pulled together some answers to similar questions:

    With the consideration that it inspect can only obtain the source for the whole function (if the source is available on the path) I can offer you this:

    import traceback
    import inspect
    import gc
    
    def giveupthefunc(frame):
        code  = frame.f_code
        globs = frame.f_globals
        functype = type(lambda: 0)
        funcs = []
        for func in gc.get_referrers(code):
            if type(func) is functype:
                if getattr(func, "func_code", None) is code:
                    if getattr(func, "func_globals", None) is globs:
                        funcs.append(func)
                        if len(funcs) > 1:
                            return None
        return funcs[0] if funcs else None
    
    
    def AssertTrue(expr, reason=None):
        print traceback.format_stack()[-2]
        frame = inspect.currentframe().f_back
        func = giveupthefunc(frame)
        if func:
            source = inspect.getsourcelines(func)
            i = source[1]
            for line in source[0]:
                print i, ":", line,
                i += 1
    
    
    
    def my_fun():
        AssertTrue(1 == 2,
                 reason='One is not equal to two')
    
    my_fun()
    

    Which produces:

    /Library/Frameworks/Python.framework/Versions/2.7/bin/python /Users/xxxx/Documents/PycharmProjects/scratchpad/test.py
      File "/Users/xxxx/Documents/PycharmProjects/scratchpad/test.py", line 35, in my_fun
        reason='One is not equal to two')
    
    33 : def my_fun():
    34 :     AssertTrue(1 == 2,
    35 :              reason='One is not equal to two')