python - 如何将非硬编码参数传递给Python装饰器?

标签 python unit-testing python-3.x decorator

我的目标是创建一个简单的单元测试装饰器,它执行一个函数,如果成功,则不执行任何操作,如果不成功,则打印“FAILURE”及其所有参数。我确实了解内置的 unittest 包。我这样做是为了学习装饰器。我不会比“如果实际等于预期,则不执行任何操作,否则打印参数”更进一步。

我找到了this function它打印出函数的所有参数:

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)

输出:

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} )

我将其更改为这样,仅当函数不等于4时才打印出参数(在没有装饰器参数的情况下实现):

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)

输出:

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

(我不明白为什么1打印在第二行。)

然后我将其更改为传入预期值 4 作为装饰器参数。如 this answer 中所述,它要求原始装饰器是一个嵌套函数:

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)

输出:

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...)

我不清楚如何将此硬编码的 4 更改为在每次函数调用时传递的 expected_value 参数。我见过的所有示例(例如 this one )都有硬编码参数。

我目前正在尝试

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)

但这离工作还很远。

如何实现可以传递给每个函数调用的装饰器参数?

最佳答案

由于装饰器包装了函数,因此您可以在调用函数时拦截函数的输入和输出。通过这种方式,您可以查找 _expected 关键字,将其删除,调用该函数,然后根据传入的预期值测试该函数的返回值。

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)

关于python - 如何将非硬编码参数传递给Python装饰器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25207830/

相关文章:

python - 使用 python 3.x 将文件写入磁盘

python - 是什么导致 http 请求覆盖 ​​file.write() 创建的内部缓冲区?

python - fpdf "UnicodeEncodeError: ' latin- 1' codec can' t 在位置 88 : ordinal not in range(256)"中编码字符 '\u2013'

python - 仅当值不是特定值时按值过滤数据帧

python - 在 appengine 中将 pytest 与 gaesessions session 中间件一起使用

java - 编写重试机制打印出日志

python - Django 响应没有内容 - 如何调试?

python - 最高负值

javascript - 如何在 JavaScript 中对原始二进制数据使用按位运算符进行 CRC 检查?

python - 使用二维 numpy 数组有效填充 pandas 数据框