5

In some circumstances, I want to print debug-style output like this:

# module test.py
def f()
  a = 5
  b = 8
  debug(a, b) # line 18

I want the debug function to print the following:

debug info at test.py: 18
function f
a = 5
b = 8

I am thinking it should be possible by using inspect module to locate the stack frame, then finding the appropriate line, looking up the source code in that line, getting the names of the arguments from there. The function name can be obtained by moving one stack frame up. (The values of the arguments is easy to obtain: they are passed directly to the function debug.)

Am I on the right track? Is there any recipe I can refer to?

max
  • 49,282
  • 56
  • 208
  • 355

4 Answers4

3

You could do something along the following lines:

import inspect

def debug(**kwargs):
  st = inspect.stack()[1]
  print '%s:%d %s()' % (st[1], st[2], st[3])
  for k, v in kwargs.items():
    print '%s = %s' % (k, v)

def f():
  a = 5
  b = 8
  debug(a=a, b=b) # line 12

f()

This prints out:

test.py:12 f()
a = 5
b = 8
NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • 1
    That's nice and simple! But I wouldn't mind more complexity if it means I don't have to repeat each variable name twice: once as keyword arg name, and once as keyword argument value. – max Mar 30 '12 at 07:15
  • You could try calling `debug(**locals())`. – Karl Knechtel Mar 30 '12 at 08:16
1

You're generally doing it right, though it would be easier to use AOP for this kinds of tasks. Basically, instead of calling "debug" every time with every variable, you could just decorate the code with aspects which do certain things upon certain events, like upon entering the function to print passed variables and it's name.

Please refer to this site and old so post for more info.

Community
  • 1
  • 1
Dmitry Reznik
  • 6,812
  • 2
  • 32
  • 27
1

Yeah, you are in the correct track. You may want to look at inspect.getargspec which would return a named tuple of args, varargs, keywords, defaults passed to the function.

import inspect

def f():
  a = 5
  b = 8
  debug(a, b)


def debug(a, b):
    print inspect.getargspec(debug)
f()
Senthil Kumaran
  • 54,681
  • 14
  • 94
  • 131
0

This is really tricky. Let me try and give a more complete answer reusing this code, and the hint about getargspec in Senthil's answer which got me triggered somehow. Btw, getargspec is deprecated in Python 3.0 and getfullarcspec should be used instead.

This works for me on a Python 3.1.2 both with explicitly calling the debug function and with using a decorator:

# from: https://stackoverflow.com/a/4493322/923794
def getfunc(func=None, uplevel=0):
    """Return tuple of information about a function

    Go's up in the call stack to uplevel+1 and returns information
    about the function found.

    The tuple contains
      name of function, function object, it's frame object,
      filename and line number"""
    from inspect import currentframe, getouterframes, getframeinfo
    #for (level, frame) in enumerate(getouterframes(currentframe())):
    #    print(str(level) + ' frame: ' + str(frame))
    caller = getouterframes(currentframe())[1+uplevel]
    # caller is tuple of:
    #  frame object, filename, line number, function
    #  name, a list of lines of context, and index within the context
    func_name = caller[3]
    frame = caller[0]
    from pprint import pprint
    if func:
        func_name = func.__name__
    else:
        func = frame.f_locals.get(func_name, frame.f_globals.get(func_name))
    return (func_name, func, frame, caller[1], caller[2])


def debug_prt_func_args(f=None):
    """Print function name and argument with their values"""
    from inspect import getargvalues, getfullargspec
    (func_name, func, frame, file, line) = getfunc(func=f, uplevel=1)
    argspec = getfullargspec(func)
    #print(argspec)
    argvals = getargvalues(frame)
    print("debug info at " + file + ': ' + str(line))
    print(func_name + ':' + str(argvals))   ## reformat to pretty print arg values here
    return func_name



def df_dbg_prt_func_args(f):
    """Decorator: dpg_prt_func_args - Prints function name and arguments

    """
    def wrapped(*args, **kwargs):
        debug_prt_func_args(f) 
        return f(*args, **kwargs) 
    return wrapped

Usage:

@df_dbg_prt_func_args
def leaf_decor(*args, **kwargs):
    """Leaf level, simple function"""
    print("in leaf")


def leaf_explicit(*args, **kwargs):
    """Leaf level, simple function"""
    debug_prt_func_args()
    print("in leaf")


def complex():
    """A complex function"""
    print("start complex")
    leaf_decor(3,4)
    print("middle complex")
    leaf_explicit(12,45)
    print("end complex")


complex()

and prints:

start complex
debug info at debug.py: 54
leaf_decor:ArgInfo(args=[], varargs='args', keywords='kwargs', locals={'args': (3, 4), 'f': <function leaf_decor at 0x2aaaac048d98>, 'kwargs': {}})
in leaf
middle complex
debug info at debug.py: 67
leaf_explicit:ArgInfo(args=[], varargs='args', keywords='kwargs', locals={'args': (12, 45), 'kwargs': {}})
in leaf
end complex

The decorator cheats a bit: Since in wrapped we get the same arguments as the function itself it doesn't matter that we find and report the ArgSpec of wrapped in getfunc and debug_prt_func_args. This code could be beautified a bit, but it works alright now for the simple debug testcases I used.

Another trick you can do: If you uncomment the for-loop in getfunc you can see that inspect can give you the "context" which really is the line of source code where a function got called. This code is obviously not showing the content of any variable given to your function, but sometimes it already helps to know the variable name used one level above your called function.

As you can see, with the decorator you don't have to change the code inside the function.

Probably you'll want to pretty print the args. I've left the raw print (and also a commented out print statement) in the function so it's easier to play around with.

Community
  • 1
  • 1
cfi
  • 10,915
  • 8
  • 57
  • 103