我想为知道位置参数的函数编写一个包装器。不幸的是,我还想允许使用命名位置参数调用包装函数(为了便于阅读)。由于包装函数实际上是一个新函数,因此它不具有旧函数的位置参数名称。
示例:这是一个包装器。
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]
会有索引一个空列表。
更糟糕的是,原始函数本身是否被包装(可能是由相同的包装器)。我们必须以保留参数列表的方式定义两个包装器。
答案 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