Python 如何忽略传递给函数的意外关键字参数?
假设我有一些函数,Python 如何忽略传递给函数的意外关键字参数?,python,function,dictionary,Python,Function,Dictionary,假设我有一些函数,f: def f (a=None): print a 现在,如果我有一个字典,比如dct={“a”:“Foo”},我可以调用f(**dct),并打印结果Foo 但是,假设我有一个字典dct2={“a”:“Foo”,“b”:“Bar”}。如果我调用f(**dct2)我会得到一个 TypeError: f() got an unexpected keyword argument 'b' 很公平。然而,在f的定义或调用中,是否有必要告诉Python忽略任何不是参数名的键?
f
:
def f (a=None):
print a
现在,如果我有一个字典,比如dct={“a”:“Foo”}
,我可以调用f(**dct)
,并打印结果Foo
但是,假设我有一个字典dct2={“a”:“Foo”,“b”:“Bar”}
。如果我调用f(**dct2)
我会得到一个
TypeError: f() got an unexpected keyword argument 'b'
很公平。然而,在f
的定义或调用中,是否有必要告诉Python忽略任何不是参数名的键?最好是允许指定默认值的方法。这可以通过使用来完成,它允许您收集dict中所有未定义的关键字参数:
def f(**kwargs):
print kwargs['a']
快速测试:
In [2]: f(a=13, b=55)
13
编辑如果仍要使用默认参数,可以使用默认值保留原始参数,但只需添加**kwargs
即可吸收所有其他参数:
In [3]: def f(a='default_a', **kwargs):
...: print a
...:
In [4]: f(b=44, a=12)
12
In [5]: f(c=33)
default_a
作为@Bas发布的答案的扩展,我建议将kwargs参数(可变长度关键字参数)作为第二个参数添加到函数中
>>> def f (a=None, **kwargs):
print a
>>> dct2 = {"a":"Foo", "b":"Bar"}
>>> f(**dct2)
Foo
这就足以证明
如果无法将函数定义更改为接受未指定的**KWARG,则可以使用python旧版本中的argspec函数或python 3.6中的签名检查方法,通过关键字参数过滤传入的字典
import inspect
def filter_dict(dict_to_filter, thing_with_kwargs):
sig = inspect.signature(thing_with_kwargs)
filter_keys = [param.name for param in sig.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD]
filtered_dict = {filter_key:dict_to_filter[filter_key] for filter_key in filter_keys}
return filtered_dict
def myfunc(x=0):
print(x)
mydict = {'x':2, 'y':3}
filtered_dict = filter_dict(mydict, myfunc)
myfunc(**filtered_dict) # 2
myfunc(x=3) # 3
@Aviendha的答案很好,根据她的帖子,在Python3.6中编写一个增强版,支持函数的关键字参数签名中的默认值
import inspect
from inspect import Parameter
import functools
from typing import Callable, Any
def ignore_unexpected_kwargs(func: Callable[..., Any]) -> Callable[..., Any]:
sig = inspect.signature(func)
params = sig.parameters.values()
def filter_kwargs(kwargs: dict) -> dict:
_params = filter(lambda p: p.kind in {Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY}, params)
res_kwargs = {
param.name: kwargs[param.name]
for param in _params if param.name in kwargs
}
return res_kwargs
def contain_var_keyword() -> bool:
return len(params) >= 1 and any(filter(lambda p: p.kind == Parameter.VAR_KEYWORD, params))
def contain_var_positional() -> bool:
return len(params) >= 1 and any(filter(lambda p: p.kind == Parameter.VAR_POSITIONAL, params))
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
kwargs = filter_kwargs(kwargs)
return func(*args, **kwargs)
ret_func = func
if not contain_var_keyword():
if contain_var_positional():
raise RuntimeError('*args not supported')
ret_func = wrapper
return ret_func
if __name__ == "__main__":
@ignore_unexpected_kwargs
def foo(a, b=0, c=3):
return a, b, c
assert foo(0, 0, 0) == (0, 0, 0)
dct = {'a': 1, 'b': 2}
assert foo(**dct) == (1, 2, 3)
@ignore_unexpected_kwargs
def bar(a, b, **kwargs):
return a, b, kwargs.get('c', 3)
assert bar(**{'a': 1, 'b': 2, 'c': 4}) == (1, 2, 4)
我解决了@Menglong_Li没有解决的一些问题,并简化了代码
import inspect
import functools
def ignore_unmatched_kwargs(f):
"""Make function ignore unmatched kwargs.
If the function already has the catch all **kwargs, do nothing.
"""
if any(param.kind == inspect.Parameter.VAR_KEYWORD for param in inspect.signature(f).parameters.values()):
return f
#
@functools.wraps(f)
def inner(*args,**kwargs):
# For each keyword arguments recognised by f,
# take their binding from **kwargs received
filtered_kwargs = {
name:kwargs[name]
for name,param in inspect.signature(f).parameters.items() if (
param.kind is inspect.Parameter.KEYWORD_ONLY or
param.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
) and
name in kwargs
}
return f(*args,**filtered_kwargs)
return inner
以下是一些广泛的测试用例:
@ignore_unmatched_kwargs
def positional_or_keywords(x,y):
return x,y
@ignore_unmatched_kwargs
def keyword_with_default(x,y,z=True):
return x,y,z
@ignore_unmatched_kwargs
def variable_length(x,y,*args,**kwargs):
return x,y,args,kwargs
@ignore_unmatched_kwargs
def keyword_only(x,*,y):
return x,y
# these test should run without error
print(
positional_or_keywords(x=3,y=5,z=10),
positional_or_keywords(3,y=5),
positional_or_keywords(3,5),
positional_or_keywords(3,5,z=10),
keyword_with_default(2,2),
keyword_with_default(2,2,z=False),
keyword_with_default(2,2,False),
variable_length(2,3,5,6,z=3),
keyword_only(1,y=3),
sep='\n'
)
# these test should raise error
print(
#positional_or_keywords(3,5,6,4),
#keyword_with_default(2,2,3,z=False),
#keyword_only(1,2),
sep='\n'
)
我用这个想法建立了我自己的。它仅针对一个非常简单的案例进行测试,但可能对某些人有用:
import inspect
def filter_dict(func, kwarg_dict):
sign = inspect.signature(func).parameters.values()
sign = set([val.name for val in sign])
common_args = sign.intersection(kwarg_dict.keys())
filtered_dict = {key: kwarg_dict[key] for key in common_args}
return filtered_dict
在此特定情况下测试:
def my_sum(first, second, opt=3):
return first + second - opt
a = {'first': 1, 'second': 2, 'third': 3}
new_kwargs = filter_dict(my_sum, a)
该示例返回
new_args={'first':1,'second':2}
,然后可以将其传递到my_sum
,作为my_sum(**new_args)
我认为这就是OP所寻找的。还要注意f(10,b=20)
将在这里打印10
,如果你只使用了**kwargs
,这是不正确的。但是如果这个函数是由其他人制作的(例如,一个开源库),我不能这么做。编辑有点误导。对于第二个,你能做的最重要的事情是把a
作为一个位置参数,而对于第一个,你不能。(除非您希望获取*args
以及**kwargs
并自己复制整个参数绑定机制。)另一方面,仅处理默认值很简单-打印kwargs.get('a','default_a')
。实际上,使用kwargs.get()
将是定义默认值的另一种方式。我的解决方案的好处是,您可以看到函数只有一个默认值,只需从函数头开始,而不必深入函数体。它现在还需要位置参数是一些小的副作用。我认为API的影响比API的内省性大得多。我从这个答案中学到了很多,但它仍然存在问题。例如,[1]它无法处理run(1,2)
,因为包装器
不接受任何位置参数,即使原始def run
声明了位置参数。[2] 不处理关键字_,这在python中是新的[3]无法识别原始函数是否已经有**kwargs。在以位置和关键字形式提供值的情况下,我让python标准决定它应该引发什么错误。在我的回答中,测试用例是带有默认值(2,2,3,z=False)的keyword\u
。这种方法有助于其他人编写函数规范,而您无法从调用代码中更改它。很好的建议。id=”dmid://uu023filterdict1623010139"