Print name and value of Python function arguments

2019-05-18 10:23发布

I want to make a debug_print() which would output the callers variable and value, and later on I would extend this to only partially print lists and dicts and so on. This post focuses only on the first part of printing callers variables and values.

This post has the following parts:

  • Current version of debug_print
  • Constructed test cases
  • Output of test cases
  • My wanted output, and problem areas
  • List of somewhat related questions

Sorry, for a somewhat lengthy post, but I just want to show I've done some research, and really would like some help to get some assistance on how to solve my issues (as listed in second to last section).

Current version of debug_print

import inspect

def debug_print(*args):

    try:  # find code_context
        # First try to use currentframe() (maybe not available in all implementations)
        frame = inspect.currentframe()
        if frame:
            # Found a frame, so get the info, and strip space from the code_context
            code_context = inspect.getframeinfo(frame.f_back).code_context[0].strip()
        else:

            # No frame, so use stack one level above us, and strip space around
            # the 4th element, code_context
            code_context = inspect.stack()[1][4][0].strip()

    finally:
         # Deterministic free references to the frame, to be on the safe side
         del frame

    print('Code context : {}'.format(code_context))
    print('Value of args: {}\n'.format(args))

Constructed test cases

# Test with plain variables
a = 0.2
b = 1.2
c = a + 1
debug_print(a, b, c, b+2)

# Test with list, list of lists, tuples and dict's
debug_print([4.1, 4.2], [[4.00, 4.01], ["4.1.0", '4.1.1']])
debug_print((5.1,   5.2), {6.1: 6.2})

# Test with func's or func aliases
def my_func():
   return 7
my_alias_func = my_func

debug_print(my_func, my_alias_func, my_alias_func())


# Test with nested func's and list slices
my_list = [1, 2, 3, 4, 5]

def first_level():
    def second_level():
         debug_print(my_list[:2], my_list[3:])

    second_level()

# Execute
first_level()

# Test with multi-line call
debug_print(a, b,
            'multi-line call', c)

Output of test cases

Code context : debug_print(a, b, c, b+2)
Value of args: (0.2, 1.2, 1.2, 3.2)

Code context : debug_print([4.1, 4.2], [[4.00, 4.01], ["4.1.0", '4.1.1']])
Value of args: ([4.1, 4.2], [[4.0, 4.01], ['4.1.0', '4.1.1']])

Code context : debug_print((5.1,   5.2), {6.1: 6.2})
Value of args: ((5.1, 5.2), {6.1: 6.2})

Code context : debug_print(my_func, my_alias_func, my_alias_func())
Value of args: (<function my_func at 0x110393668>, <function my_func at 0x110393668>, 7)

Code context : debug_print(my_list[:2], my_list[3:])
Value of args: ([1, 2], [4, 5])

Code context : 'multi-line call', c)
Value of args: (0.2, 1.2, 'multi-line call', 1.2)

Wanted output, and problem areas

I would love for something like the following to be output:

a: 0.2;  b: 1.2;  c: 1.2; 3.2
<list>: [4.1, 4.2];  <list of lists>: [[4.0, 4.01], ['4.1.0', '4.1.1']]
<tuple>: (5.1, 5.2);  <dict>: {6.1: 6.2}
func: my_func;  func: my_alias_func -> my_func;  my_func(): 7
my_list[:2]: [1, 2];  my_list[3:]: [4, 5]

I do however see from related issues that this is maybe setting the bar a little high. But the closer I get, the better it would be.

Ideally I could loop through some dict which the original argument code as key, but I would also be most satisfied with a way to extract the true argument code from the code_context and then combine these with the actual args.

However I can not simply split the code context on , as that might also be a part of lists, dict's or tuples. If some python parser could be used to split the code context, this might be an alternate route to explore. But I would like to avoid using eval.

So far my search has not revealed any place where I can get the functions actually argument list with both code and values. (Have seen references to f_globals or func_globals but they list everything available to the module, and I found no way to reconnect these to a variable arguemnt list).

On a sidenote: I know it is possible to use variations over debug_print(**kwargs) and then having statements like debug_print(a=a, b=b; c=c; sum=b+2), but it would be even better to avoid that duplication when writing the debug statements.

List of somewhat related issues

There has been questions in the past related to similar issues, but whilst most of them deal with fixed arguments, or just displaying names, I would like to show both name and value, if available. Or alternatively a guide to parsing the code context to match the arguments given. But here goes some of the related questions:

1条回答
够拽才男人
2楼-- · 2019-05-18 11:03

I'm not sure that any syntax can be better than what you already got, but:

def pprint(arg):
    of = None
    if not isinstance(arg, (str, bytes)):
        try:
            of = type(arg[0]).__name__
        except (IndexError, TypeError, KeyError):
            of = None
    if of is None:
        if hasattr(arg, '__name__'):
            return '{}({})'.format(type(arg).__name__, arg.__name__)
        else:
            return '{}({})'.format(type(arg).__name__, repr(arg))
    else:
        return '{}<{}>{}'.format(type(arg).__name__, of, repr(arg))

def debug_print(*args):

    try:  # find code_context
            # First try to use currentframe() (maybe not available in all implementations)
        frame = inspect.currentframe()
        if frame:
            # Found a frame, so get the info, and strip space from the code_context
            code_context = inspect.getframeinfo(frame.f_back).code_context[0].strip()
        else:
            # No frame, so use stack one level above us, and strip space around
            # the 4th element, code_context
            code_context = inspect.stack()[1][4][0].strip()
    finally:
             # Deterministic free references to the frame, to be on the safe side
        del frame

    print('Code context : {}'.format(code_context))
    print('Value of args: {}\n'.format('; '.join(pprint(arg) for arg in args)))

Gives:

Code context : debug_print(a, b, c,  b + 2)
Value of args: float(0.2); float(1.2); float(1.2); float(3.2)

Code context : debug_print([4.1, 4.2], [[4.00, 4.01], ["4.1.0", '4.1.1']])
Value of args: list<float>[4.1, 4.2]; list<list>[[4.0, 4.01], ['4.1.0', '4.1.1']]

Code context : debug_print((5.1,   5.2), {6.1: 6.2})
Value of args: tuple<float>(5.1, 5.2); dict({6.1: 6.2})

Code context : debug_print(my_func, my_alias_func, my_alias_func())
Value of args: function(my_func); function(my_func); int(7)

Code context : debug_print(my_list[:2], my_list[3:])
Value of args: list<int>[1, 2]; list<int>[4, 5]

Code context : 'multi-line call', c)
Value of args: float(0.2); float(1.2); str('multi-line call'); float(1.2)

Still, I prefer the repr builtin over my pprint function:

  • pprint([1, 'a'])

Gives list<int>(1, 'a') which is obviously wrong, and we can't do better, do you really want list<int or string> ?

repr gives [1, 'a'] which is readable and correct.

  • pprint(1.3)

Gives <float>(1.3), like I can't see that 1.3 is a float ? repr gives 1.3, which is clearly enough.

  • ...
查看更多
登录 后发表回答