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"