如何忽略传递给函数的意外关键字参数?

时间:2014-10-22 19:32:06

标签: 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只是忽略任何非参数名称的键?优选允许指定默认值的方法。

6 个答案:

答案 0 :(得分:23)

作为@Bas发布的答案的扩展,我建议将kwargs参数(变长关键字参数)作为函数的第二个参数添加

>>> def f (a=None, **kwargs):
    print a


>>> dct2 = {"a":"Foo", "b":"Bar"}
>>> f(**dct2)
Foo

这一定就足够了

  1. 只是忽略任何非参数名称的键
  2. 但是,它缺少参数的默认值,这是一个很好的功能,保持

答案 1 :(得分:13)

这可以通过使用**kwargs来完成,它允许您收集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

答案 2 :(得分:4)

如果您无法更改函数定义以使用未指定的** kwargs,则可以使用旧版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

答案 3 :(得分:1)

@Aviendha的回答很好,根据她的帖子,在Python 3.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)

答案 4 :(得分:0)

我解决了@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'
)

答案 5 :(得分:0)

我使用了 Aviendha's 的想法来构建我自己的想法。它仅针对一个非常简单的案例进行了测试,但它可能对某些人有用:

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)