在 Python 中,如何定义一个函数包装器来验证具有特定名称的参数?
In Python, how to define a function wrapper which validates an argument with a certain name?
我正在编写几个接受名为 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
问题是,这仅在 policy
是函数的第一个位置参数时有效。但是,我想允许 policy
出现在任何位置。
具体来说,这里有一些名为 make_decision
和 make_informed_decision
的(虚拟)函数,它们在不同位置接受参数 policy
,以及一些测试用例:
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__])
目前除第三个测试外所有测试都通过了,因为第一个位置参数 'allow'
被解释为 policy
而不是应有的 data
。
如何调整 validate_policy
装饰器,使所有测试都通过?
您可以使用 inspect
模块的 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
这是另一个使用 inspect.getcallargs 的解决方案:
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(*args, **kwargs):
call_args = inspect.getcallargs(function, *args, **kwargs)
if 'policy' in call_args:
if call_args['policy'] not in ['allow', 'deny']:
raise ValueError("The policy must be either 'allow' or 'deny'.")
return function(*args, **kwargs)
return wrapped_function
它使所有测试都通过。
我正在编写几个接受名为 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
问题是,这仅在 policy
是函数的第一个位置参数时有效。但是,我想允许 policy
出现在任何位置。
具体来说,这里有一些名为 make_decision
和 make_informed_decision
的(虚拟)函数,它们在不同位置接受参数 policy
,以及一些测试用例:
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__])
目前除第三个测试外所有测试都通过了,因为第一个位置参数 'allow'
被解释为 policy
而不是应有的 data
。
如何调整 validate_policy
装饰器,使所有测试都通过?
您可以使用 inspect
模块的 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
这是另一个使用 inspect.getcallargs 的解决方案:
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(*args, **kwargs):
call_args = inspect.getcallargs(function, *args, **kwargs)
if 'policy' in call_args:
if call_args['policy'] not in ['allow', 'deny']:
raise ValueError("The policy must be either 'allow' or 'deny'.")
return function(*args, **kwargs)
return wrapped_function
它使所有测试都通过。