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 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, 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) 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?
assert_expected_func_params, why do you need a decorator?@decorator(arg)syntax for the reason @TheSoundDefense provides, but you can do the manual equivalent, i.e.func = decorator(arg)(func).1is appearing is because that's the output of yourgetAfunction (which just returns whatever paramais). You'd get the same result regardless or not if you applied the decorator.