How to pass non-hard-coded parameter to Python decorator?
Question: My goal is to create a trivial unit test decorator, which executes a function
and, if it succeeds, do nothing, if it doesn't, print "FAILURE" and all its
parameters. I do know about the builtin `unittest` package. I'm doing this to
learn decorators. I'm not taking this any farther than "if actual equals
expected, do nothing, else print params".

I found [this function](http://stackoverflow.com/a/25206079/2736496) which
prints out all of a function's parameters:

    
    
    def dumpArgs(func):
        '''Decorator to print function call details - parameters names and effective values'''
        def wrapper(*func_args, **func_kwargs):
            arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
            args = func_args[:len(arg_names)]
            defaults = func.__defaults__ or ()
            args = args + defaults[len(defaults) - (func.__code__.co_argcount - len(args)):]
            params = list(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.__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)
    

Output:

    
    
    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 = {'g': 12.9, 'd': 6} )
    

I changed it to this, which prints out the parameters only if the function
does not equal `4` (implemented without a decorator param):

    
    
    def get_all_func_param_name_values(func, *func_args, **func_kwargs):
        arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
        args = func_args[:len(arg_names)]
        defaults = func.__defaults__ or ()
        args = args + defaults[len(defaults) - (func.__code__.co_argcount - len(args)):]
        params = list(zip(arg_names, args))
        args = func_args[len(arg_names):]
        if args: params.append(('args', args))
        if func_kwargs: params.append(('kwargs', func_kwargs))
        return  '(' + ', '.join('%s = %r' % p for p in params) + ')'
    
    def dumpArgs(func):
        '''Decorator to print function call details - parameters names and effective values'''
        def wrapper(*func_args, **func_kwargs):
            a = func(*func_args, **func_kwargs)
            if(a != 4):
                return  a
            print("FAILURE: " + func.__name__ + get_all_func_param_name_values(func, *func_args, **func_kwargs))
            return a
        return wrapper
    
    @dumpArgs
    def getA(a, b = 4, c = 'blah-blah', *args, **kwargs):
        return  a
    
    getA(1)
    getA(1, 3)
    getA(4, d = 5)
    getA(1, 2, 3, 4, 5, d = 6, g = 12.9)
    

Output:

    
    
    FAILURE: getA(a = 4, b = 4, c = 'blah-blah', kwargs = {'d': 5})
    Out[21]: 1
    

(I don't understand why the `1` is printed in the second line.)

I then changed it to pass in the expected value, `4`, as decorator parameter.
As described in [this answer](http://stackoverflow.com/a/10176276/2736496), it
requires that the original decorator be a nested function:

    
    
    def get_all_func_param_name_values(func, *func_args, **func_kwargs):
        arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
        args = func_args[:len(arg_names)]
        defaults = func.__defaults__ or ()
        args = args + defaults[len(defaults) - (func.__code__.co_argcount - len(args)):]
        params = list(zip(arg_names, args))
        args = func_args[len(arg_names):]
        if args: params.append(('args', args))
        if func_kwargs: params.append(('kwargs', func_kwargs))
        return  '(' + ', '.join('%s = %r' % p for p in params) + ')'
    
    def dumpArgs(expected_value):
        def dumpArgs2(func):
            '''Decorator to print function call details - parameters names and effective values'''
            def wrapper(*func_args, **func_kwargs):
                a = func(*func_args, **func_kwargs)
                if(a == expected_value):
                    return  a
                print("FAILURE: " + func.__name__ + get_all_func_param_name_values(func, *func_args, **func_kwargs))
                return a
            return wrapper
        return  dumpArgs2
    
    @dumpArgs(4)
    def getA(a, b = 4, c = 'blah-blah', *args, **kwargs):
        return  a
    
    getA(1)
    getA(1, 3)
    getA(4, d = 5)
    getA(1, 2, 3, 4, 5, d = 6, g = 12.9)
    

Output:

    
    
    FAILURE: getA(a = 1, b = 4, c = 'blah-blah')
    FAILURE: getA(a = 1, b = 3, c = 'blah-blah')
    FAILURE: getA(a = 1, b = 2, c = 3, args = (4, 5), kwargs = {'g': 12.9, 'd': 6})
    Out[31]: 1
    

(Again, that `1`...)

I'm not clear on how to change this hard-coded `4` to an `expected_value`
parameter, that is passed through at every function call. All the examples
I've seen (like [this one](http://stackoverflow.com/questions/10176226/how-to-
pass-extra-arguments-to-python-decorator)) have hard-coded parameters.

I currently experimenting with

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

But it's far from working.

How do I implement a decorator parameter that I can pass in to _every function
call_?


Answer: Since a decorator wraps the function, you can intercept the input and output
of the function when it is called. In this way, you could look for an
`_expected` keyword, strip it out, call the function, then test the return
value of the function against the passed in expected value.

    
    
    from functools import wraps
    
    _empty = object()  # sentinel value used to control testing
    
    def dump_ne(func):
        @wraps(func)
        def decorated(*args, **kwargs):
            # remove the expected value from the actual call kwargs
            expected = kwargs.pop('_expected', _empty)
    
            # call the function with rest of args and kwargs
            result = func(*args, **kwargs)
    
            # only test when _expected was passed in the kwargs
            # only print when the result didn't equal expected
            if expected is not _empty and expected != result:
                print('FAIL: func={}, args={}, kwargs={}'.format(func.__name__, args, kwargs))
    
            return result
    
        return decorated
    
    @dump_ne
    def cool(thing):
        return thing.upper()
    
    print(cool('cat'))  # prints 'CAT', test isn't run
    
    for thing in ('cat', 'ice', 'cucumber'):
        print(cool(thing, _expected='CUCUMBER'))
        # dumps info for first 2 calls (cat, ice)
    

