25

Let's say I have a function:

def foo(a=None, b=None, c=None):
  return "a:%s, b:%s, c:%s" % (a, b, c)

I have a dictionary with some (or none) of the arguments above, but also with keys that are not named arguments in the function, e.g.:

d = {'a': 1, 'x': 4, 'b': 2, 'y': 5}

If I call the following I will get an error, because 'x' and 'y' are not keyword arguments in the foo function.

foo(**d)  # error

Is there an elegant way of passing the arguments from a dictionary to a function, but only those values with keys that match the function arguments.

Please correct me if my argument/parameter terminology is off.

101
  • 8,514
  • 6
  • 43
  • 69
Mads Skjern
  • 5,648
  • 6
  • 36
  • 40

3 Answers3

34
def foo(a = None, b=None, c=None,**extras):
    return "a:%s, b:%s, c:%s" % (a, b, c)

here the **extras will collect all the extra named/keyword arguments.

Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • Only works if you are allowed to modify foo and don't mind it throwing away unknown arguments. Probably not what you want in most cases and is not possible if the function is from some third party package. – Christopher Barber May 15 '19 at 20:37
12

Interesting question. I think that most people in real life use @Ashwini Chaudhary approach.

I do agree with @Rodrigue that there are times you can't modify the call signature of the function (someone else's module perhaps).

When that happens, Use a function decorator

from inspect import getargspec
from funtools import wraps

def allow_kwargs(foo):
    argspec = getargspec(foo)
    # if the original allows kwargs then do nothing
    if  argspec.keywords:
        return foo
    @wraps(foo)
    def newfoo(*args, **kwargs):
        #print "newfoo called with args=%r kwargs=%r"%(args,kwargs)
        some_args = dict((k,kwargs[k]) for k in argspec.args if k in kwargs) 
        return foo(*args, **some_args)
    return newfoo


# with your function:
@allow_kwargs
def foo(a = None, b=None, c=None):
  return "a:%s, b:%s, c:%s " % (a,b,c)

# with someone_elses function:
from some_place import foo
foo = allow_kwargs(foo)

@wraps from functools keeps the __name__ and __doc__ strings in tact. You could also:

  • look at FunctionMaker from the decorators module but this should be a more reusable approach.
  • modify newfoo to allow extra non keyword vars to be not passed through.
Phil Cooper
  • 5,747
  • 1
  • 25
  • 41
11

@Ashwini Chaudhary has a very pythonic way of solving your problem. However, it requires changing the signature of your foo function.

If you don't want to change your function signature, you can use introspection to find out what arguments your function expects:

arg_count = foo.func_code.co_argcount
args = foo.func_code.co_varnames[:arg_count]

args_dict = {}
for k, v in d.iteritems():
    if k in args:
        args_dict[k] = v

foo(**args_dict)
Rodrigue
  • 3,617
  • 2
  • 37
  • 49