Expand only the key-value pairs required by a function

Viewed 165

Let's say we have a function that takes some arguments and a dict that is a superset of values required to invoke the function:

d = {"a": 1, "b": 2, "c": 3}

def foo(a, b):
   print(a, b)

# this won't work
foo(**d)

# this works but is tedious the more values there are
foo(**{k: v for k,v in d.items() if k not in ("c")})

Is there a more elegant solution?

4 Answers

you could use keyword arguments:

def foo(**kwarg):
    print(kwarg['a'], kwarg['b'])

foo(**d)

output:

1 2

Assuming you do not want to modify foo's function signature, you can use foo.__code__.co_varnames to get the argument names of foo if you are using cpython

foo(**{k: d[k] for k in  foo.__code__.co_varnames})

Instead of doing this:

def foo(a, b):
   print(a, b)

You could do this:

def foo(a, b, **kwargs):
   print(a, b)

Then the function would just ignore all the unneeded arguments.

However, in my view, there are some problems with this approach.

  1. Sometimes you don't own the source code of the function. Of course, you can make lots of wrapper functions. If you do that manually, you'll have to change the signatures of your helper functions every time the library authors modify the function signature.

  2. This solution can make some bugs harder to find. Maybe it's better to explicitly state that you're only using some of the elements from the dictionary.

You can still make it easier using the inspect module of the standard library.

a) Make a decorator that makes the function filter its keyword arguments.

For instance:

import inspect

def filter_kwargs(f):
  arg_names = inspect.getargspec(f).args
  def _f(*args, **kwargs):
    return f(*args, **{k: v for k, v in kwargs.items() if k in arg_names})
  return _f

# Use it like this:
filter_kwargs(foo)(a_dict)

b) You can create a function that transforms the argument to fit the function

def fit_for(f, kwargs):
  arg_names = inspect.getargspec(f).args
  return {k: v for k, v in kwargs.items() if k in arg_names}

foo(fit_for(foo, a_dict))

Optionally, you can expand this function to also take into account the *args and **kwargs in the original function.


If this is a recurring pattern in your code, you can just pass the dict as a single argument.

Also, as @chepner pointed out in the comments, consider creating a class.

You can do something like:

d = {"a": 1, "b": 2, "c": 3}
def foo(a, b, *arg, **kwarg):
    print(a, b)

foo(**d)
Related