How does one ignore unexpected keyword arguments passed to a function?

Suppose I have some function, f:

def f (a=None):
    print a

Now, if I have a dictionary such as dct = {"a":"Foo"}, I may call f(**dct) and get the result Foo printed.

However, suppose I have a dictionary dct2 = {"a":"Foo", "b":"Bar"}. If I call f(**dct2) I get a

TypeError: f() got an unexpected keyword argument 'b'

Fair enough. However, is there anyway to, in the definition of f or in the calling of it, tell Python to just ignore any keys that are not parameter names? Preferable a method that allows defaults to be specified.


Solution 1:

As an extension to the answer posted by @Bas, I would suggest to add the kwargs arguments (variable length keyword arguments) as the second parameter to the function

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


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

This would necessarily suffice the case of

  1. to just ignore any keys that are not parameter names
  2. However, it lacks the default values of parameters, which is a nice feature that it would be nice to keep

Solution 2:

If you cannot change the function definition to take unspecified **kwargs, you can filter the dictionary you pass in by the keyword arguments using the argspec function in older versions of python or the signature inspection method in 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

Solution 3:

This can be done by using **kwargs, which allows you to collect all undefined keyword arguments in a dict:

def f(**kwargs):
    print kwargs['a']

Quick test:

In [2]: f(a=13, b=55)
13

EDIT If you still want to use default arguments, you keep the original argument with default value, but you just add the **kwargs to absorb all other arguments:

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

Solution 4:

I used Aviendha's idea to build my own. It is only tested for a very simple case but it might be useful for some people:

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

Tested on this specific case:

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)

The example returns new_args = {'first': 1, 'second': 2} which can then be passed to my_sum as my_sum(**new_args)