简体   繁体   中英

Unpacking keyword arguments, but only the ones that match the function

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, eg:

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.

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 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)

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM