Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorator that prints function call details (parameters names and effective values)?

Tags:

python

I want to make a function that being a decorator to another function will print that function call details - parameters names and effective values. My current implementation is this.

def describeFuncCall(func):
    """
    Decorator to print function call details.

    parameters names and effective values.
    """

    def wrapper(*func_args, **func_kwargs):
        print "func_code.co_varnames =", func.func_code.co_varnames
        print "func_code.co_argcount =", func.func_code.co_argcount
        print "func_args =", func_args
        print "func_kwargs =", func_kwargs
        params = []
        for argNo in range(func.func_code.co_argcount):
            argName = func.func_code.co_varnames[argNo]
            argValue = (
                func_args[argNo]
                if argNo < len(func_args)
                else func.func_defaults[argNo - func.func_code.co_argcount]
            )
            params.append((argName, argValue))
        for argName, argValue in func_kwargs.items():
            params.append((argName, argValue))
        params = [argName + " = " + repr(argValue)
                  for argName, argValue in params]
        print (func.__name__ + " ( " + ", ".join(params) + " )")
        return func(*func_args, **func_kwargs)

    return wrapper


@describeFuncCall
def test(a, b=4, c="blah-blah", *args, **kwargs):
    pass


test(1)
# test(1, 3)
# test(1, d = 5)
test(1, 2, 3, 4, 5, d=6, g=12.9)

Kinda works, but with some bugs:

For call

test(1, 2, 3, 4, 5, d = 6, g = 12.9)

it prints

test ( a = 1, b = 2, c = 3, d = 6, g = 12.9 ).

The expected result is

test ( a = 1, b = 2, c = 3, args = [4, 5], kwargs = {'d': 6, 'g': 12.9} )

I got stuck here. Can you help me to find the right solution?

like image 892
warvariuc Avatar asked Jun 01 '11 11:06

warvariuc


People also ask

What is a function decorator?

By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

What are the parameters of the print numbers function?

The print function parameters are: *objects – an object/objects to be printed. sep – the separator between multiple printed objects. end – the character/string printed at the end after the object.

What are the function and class decorators used for?

In Python, decorators can be either functions or classes. In both cases, decorating adds functionality to existing functions. When we decorate a function with a class, that function becomes an instance of the class. We can add functionality to the function by defining methods in the decorating class.

Which keyword is used for decorating a function in Python?

However, Python provides a much easier way for us to apply decorators. We simply use the @ symbol before the function we'd like to decorate.


2 Answers

Here is the updated version for Python 3.6+

import inspect


def dump_args(func):
    """
    Decorator to print function call details.

    This includes parameters names and effective values.
    """

    def wrapper(*args, **kwargs):
        func_args = inspect.signature(func).bind(*args, **kwargs).arguments
        func_args_str = ", ".join(map("{0[0]} = {0[1]!r}".format, func_args.items()))
        print(f"{func.__module__}.{func.__qualname__} ( {func_args_str} )")
        return func(*args, **kwargs)

    return wrapper


@dump_args
def test(a, b=4, c="blah-blah", *args, **kwargs):
    pass


test(1)
test(1, 3)
test(1, d=5)
test(1, 2, 3, 4, 5, d=6, g=12.9)

Old version


Working version with default values:

def dumpArgs(func):
    '''Decorator to print function call details - parameters names and effective values'''
    def wrapper(*func_args, **func_kwargs):
        arg_names = func.func_code.co_varnames[:func.func_code.co_argcount]
        args = func_args[:len(arg_names)]
        defaults = func.func_defaults or ()
        args = args + defaults[len(defaults) - (func.func_code.co_argcount - len(args)):]
        params = zip(arg_names, args)
        args = func_args[len(arg_names):]
        if args: params.append(('args', args))
        if func_kwargs: params.append(('kwargs', func_kwargs))
        print func.func_name + ' (' + ', '.join('%s = %r' % p for p in params) + ' )'
        return func(*func_args, **func_kwargs)
    return wrapper  

@dumpArgs
def test(a, b = 4, c = 'blah-blah', *args, **kwargs):
    pass

test(1)
test(1, 3)
test(1, d = 5)
test(1, 2, 3, 4, 5, d = 6, g = 12.9)

Result:

>>> test  (  a = 1, b = 4, c = 'blah-blah' )
test  (  a = 1, b = 3, c = 'blah-blah' )
test  (  a = 1, b = 4, c = 'blah-blah', kwargs = {'d': 5} )
test  (  a = 1, b = 2, c = 3, args = (4, 5), kwargs = {'d': 6, 'g': 12.9} )
like image 105
warvariuc Avatar answered Oct 13 '22 11:10

warvariuc


Sorry its a bit messy. I modified some code from Easy Dump of Function Arguments in the PythonDecoratorLibrary.

def dump_args(func):
    "This decorator dumps out the arguments passed to a function before calling it"
    argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
    fname = func.func_name
    def echo_func(*args,**kwargs):
        print fname, "(", ', '.join(
            '%s=%r' % entry
            for entry in zip(argnames,args[:len(argnames)])+[("args",list(args[len(argnames):]))]+[("kwargs",kwargs)]) +")"
    return echo_func

@dump_args
def test(a, b = 4, c = 'blah-blah', *args, **kwargs):
    pass

test(1, 2, 3, 4, 5, d = 6, g = 12.9)

Output:

test ( a=1, b=2, c=3, args=[4, 5], kwargs={'d': 6, 'g': 12.9})
like image 27
robert king Avatar answered Oct 13 '22 11:10

robert king