在Python中,如何定义函数包装器来验证具有特定名称的参数?

我正在编写几个函数,它们接受一个称为policy的参数,该参数仅允许具有某些值(即“ allow”或“ deny”).如果没有,我希望引发ValueError.

为简便起见,我想为此定义一个装饰器.到目前为止,我已经提出了以下建议:

def validate_policy(function):
    '''Wrapper which ensures that if the function accepts a 'policy' argument, that argument is either 'allow' or 'deny'.'''
    def wrapped_function(policy, *args, **kwargs):
        if policy not in ['allow', 'deny']:
            raise ValueError("The policy must be either 'allow' or 'deny'.")
        return function(policy, *args, **kwargs)
    return wrapped_function

问题是,这仅在策略是函数的第一个位置参数时才有效.但是,我想允许政策出现在任何位置.

具体来说,以下是一些称为make_decision和make_informed_decision的(虚拟)函数,它们在不同的位置接受参数策略,以及一些与之配套的测试用例:

import pytest

@validate_policy
def make_decision(policy):      # The 'policy' might be the first positional argument
    if policy == 'allow':
        print "Allowed."
    elif policy == 'deny':
        print "Denied."

@validate_policy
def make_informed_decision(data, policy):   # It also might be the second one
    if policy == 'allow':
        print "Based on the data {data} it is allowed.".format(data=data)
    elif policy == 'deny':
        print "Based on the data {data} it is denied.".format(data=data)


'''Tests'''
def test_make_decision_with_invalid_policy_as_positional_argument():
    with pytest.raises(ValueError):
        make_decision('foobar')

def test_make_decision_with_invalid_policy_as_keyword_argument():
    with pytest.raises(ValueError):
        make_decision(policy='foobar')

def test_make_informed_decision_with_invalid_policy_as_positional_argument():
    with pytest.raises(ValueError):
        make_informed_decision("allow", "foobar")

def test_make_informed_decision_with_invalid_policy_as_keyword_argument():
    with pytest.raises(ValueError):
        make_informed_decision(data="allow", policy="foobar")


if __name__ == "__main__":
    pytest.main([__file__])

当前,除第三个测试外,所有测试都通过了,因为第一个位置参数“允许”被解释为策略,而不是应有的数据.

如何调整validate_policy装饰器,使所有测试通过?

最佳答案
您可以使用检查模块的Signature.bind功能:

import inspect

def validate_policy(function):
    '''Wrapper which ensures that if the function accepts a 'policy' argument, that argument is either 'allow' or 'deny'.'''
    signature= inspect.signature(function)
    def wrapped_function(*args, **kwargs):
        bound_args= signature.bind(*args, **kwargs)
        bound_args.apply_defaults()
        if bound_args.arguments.get('policy') not in ['allow', 'deny']:
            raise ValueError("The policy must be either 'allow' or 'deny'.")
        return function(*args, **kwargs)
    return wrapped_function

转载注明原文:在Python中,如何定义函数包装器来验证具有特定名称的参数? - 代码日志