将** kwargs参数转换为位置

时间:2015-10-31 07:02:37

标签: python

我想为知道位置参数的函数编写一个包装器。不幸的是,我还想允许使用命名位置参数调用包装函数(为了便于阅读)。由于包装函数实际上是一个新函数,因此它不具有旧函数的位置参数名称。

示例:这是一个包装器。

def double_second_argument(f):

    def g(*args):
        newargs = list(args)
        newargs[1] *= 2
        return f(*newargs)

    return g

这是一个功能。

def add(x, y):
    return x + y

我包装了这个函数。

add_twice = double_second_argument(add)

我在没有和使用命名参数的情况下调用该函数。

>>> add_twice(3, 5)
13
>>> add_twice(x=3, y=5) # = 13
TypeError: g() got an unexpected keyword argument 'x'

我收到错误,因为g没有**kwargs,而添加它会把错误变成IndexError,因为newargs[1]会有索引一个空列表。

更糟糕的是,原始函数本身是否被包装(可能是由相同的包装器)。我们必须以保留参数列表的方式定义两个包装器。

2 个答案:

答案 0 :(得分:1)

inspect.Signature.bind可用于此目的。

from functools import wraps
from inspect import signature, BoundArguments, Parameter


def positional_argument_wrapper(func):

    func_signature = signature(func)
    if any(v.kind == Parameter.VAR_KEYWORD
           for v in func_signature.parameters.values()):
        raise TypeError('Keyword-only arguments are not supported by '
                        'positional_argument_wrapper')

    @wraps(func)
    def wrapped_func(*args, **kwargs):
        bound_arguments: BoundArguments = func_signature.bind(*args, **kwargs)
        bound_arguments.apply_defaults()
        assert len(bound_arguments.kwargs) == 0
        print(f'The positional arguments are {bound_arguments.args}')
        return func(*args, **kwargs)
    return wrapped_func


@positional_argument_wrapper
def myfunc1(x=None, y=7):
    print(f'myfunc called with x={x} and y={y}')


# prints: The positional arguments are (3,)
#         myfunc called with x=3 and y=7
myfunc1(x=3)
# prints: The positional arguments are (None, 3)
#         myfunc called with x=None and y=3
myfunc1(y=3)

# raises: TypeError: Keyword-only arguments are not supported by positional_argument_wrapper
@positional_argument_wrapper
def myfunc1(x=None, y=7, **kwargs):
    pass

答案 1 :(得分:0)

inspect.signature用于获取函数的签名,即使函数被(正确)包装也是如此。 (参见PEP-3062/Implementation。)这可用于获取参数名称,然后将包装函数包装在另一个包装器中,该包装器将提取kwargs以放入args

from inspect import signature
from functools import wraps

def expands_positional_args(f):

    # Save the parameter list.
    params = list(signature(f).parameters.keys())

    def wrap(g):

        @wraps(g)
        def kw_to_positional(*args, **kwargs):

            if len(args) < len(params): # not enough args

                kwargs = kwargs.copy()
                args = list(args)

                # Extract and remove the args from kwargs
                args.extend(kwargs.pop(p) for p in params[len(args):])

            return g(*args)

        return kw_to_positional

    return wrap

(注意:这不处理默认args,假设f没有关键字参数,并为缺少的参数引发KeyError。我修复了这些问题in this version。)

现在,我们有:

>>> def double_second_argument(f):
...     @expands_positional_args(f)
...     def g(*args):
...         newargs = list(args)
...         newargs[1] *= 2
...         return f(*newargs)
...     return g
...
>>> def add(x, y):
...     return x + y
...
>>>
>>> add_twice = double_second_argument(add)
>>>
>>> add_twice(3, 5) # = 13
13
>>> add_twice(x=3, y=5) # = 13
13