How to pass non-hard-coded parameter to Python decorator?
我的目标是创建一个简单的单元测试装饰器,它执行一个函数,如果成功,什么也不做,如果不成功,打印"failure"及其所有参数。我知道内置的
我找到了这个函数,它打印出一个函数的所有参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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) |
输出:
1 2 3 4 | 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} ) |
号
我把它改成了这个,它只在函数不等于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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) |
输出:
1 2 | FAILURE: getA(a = 4, b = 4, c = 'blah-blah', kwargs = {'d': 5}) Out[21]: 1 |
。
(我不明白为什么在第二行打印
然后我将其更改为传入预期值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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) |
输出:
1 2 3 4 | 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 |
。
(再说一次,那个
我不清楚如何将这个硬编码的
我目前正在试验
1 2 3 4 | 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) |
。
但这远远没有起作用。
如何实现可以传递给每个函数调用的decorator参数?
因为修饰符包装了函数,所以可以在调用函数时截取它的输入和输出。通过这种方式,您可以查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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) |