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

Posted

技术标签:

【中文标题】在 Python 中,如何定义一个函数包装器来验证具有特定名称的参数?【英文标题】:In Python, how to define a function wrapper which validates an argument with a certain name? 【发布时间】:2017-08-04 22:23:36 【问题描述】:

我正在编写几个函数,它们接受一个名为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_decisionmake_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 装饰器以使所有测试都通过?

【问题讨论】:

使策略成为必需的关键字参数。显式优于隐式。 Rick Teachey,这似乎是个好主意,除了我的代码库是在 Python 2 中,所以 PEP 3102 还没有实现。我认为这就是您将policy 设为必需的关键字参数的意思? 无赖......... 【参考方案1】:

你可以使用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

【讨论】:

【参考方案2】:

这是另一个使用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

它使所有测试通过。

【讨论】:

以上是关于在 Python 中,如何定义一个函数包装器来验证具有特定名称的参数?的主要内容,如果未能解决你的问题,请参考以下文章

Python:异常装饰器。如何保留堆栈跟踪

如何在 Python 中使用类装饰器来混合行为?

boost-python:如何提供自定义构造函数包装函数?

如何使用python请求将经过身份验证的POST请求编码到API?

如何创建一个装饰器来装饰生成器函数? [关闭]

Python开发第十四篇装饰器