I am looking for the best way to combine a function with a dictionary that contains more items than the function's inputs
basic **kwarg unpacking fails in this case:
def foo(a,b):
return a + b
d = {'a':1,
'b':2,
'c':3}
foo(**d)
--> TypeError: foo() got an unexpected keyword argument 'c'
After some research I came up with the following approach:
import inspect
# utilities
def get_input_names(function):
'''get arguments names from function'''
return inspect.getargspec(function)[0]
def filter_dict(dict_,keys):
return {k:dict_[k] for k in keys}
def combine(function,dict_):
'''combine a function with a dictionary that may contain more items than the function's inputs '''
filtered_dict = filter_dict(dict_,get_input_names(function))
return function(**filtered_dict)
# examples
def foo(a,b):
return a + b
d = {'a':1,
'b':2,
'c':3}
print combine(foo,d)
--> 3
My question is: is this a good way of dealing with this problem, or is there a better practice or is there a mechanism in the language that I'm missing perhaps?
Note that when you are working with multiple parameters, the function call must have the same number of arguments as there are parameters, and the arguments must be passed in the same order.
Passing Dictionary as kwargs “ kwargs ” stands for keyword arguments. It is used for passing advanced data objects like dictionaries to a function because in such functions one doesn't have a clue about the number of arguments, hence data passed is be dealt properly by adding “**” to the passing type.
Use *args to take multiple arguments. Show activity on this post. Show activity on this post. If you want to avoid combining the dictionaries, then provide them as arguments rather than keyword arguments, and set their defaults to something (such as None ), and convert None to {} and otherwise copy the dictionary.
How about making a decorator that would filter allowed keyword arguments only:
import inspect
def get_input_names(function):
'''get arguments names from function'''
return inspect.getargspec(function)[0]
def filter_dict(dict_,keys):
return {k:dict_[k] for k in keys}
def filter_kwargs(func):
def func_wrapper(**kwargs):
return func(**filter_dict(kwargs, get_input_names(func)))
return func_wrapper
@filter_kwargs
def foo(a,b):
return a + b
d = {'a':1,
'b':2,
'c':3}
print(foo(**d))
What is nice about this decorator is that it is generic and reusable. And you would not need to change the way you call and use your target functions.
All of these answers are wrong.
It is not possible to do what you are asking, because the function might be declared like this:
def foo(**kwargs):
a = kwargs.pop('a')
b = kwargs.pop('b')
if kwargs:
raise TypeError('Unexpected arguments: %r' % kwargs)
Now, why on earth would anyone write that?
Because they don't know all of the arguments ahead of time. Here's a more realistic case:
def __init__(self, **kwargs):
for name in self.known_arguments():
value = kwargs.pop(name, default)
self.do_something(name, value)
super().__init__(**kwargs) # The superclass does not take any arguments
And here is some real-world code which actually does this.
You might ask why we need the last line. Why pass arguments to a superclass that doesn't take any? Cooperative multiple inheritance. If my class gets an argument it does not recognize, it should not swallow that argument, nor should it error out. It should pass the argument up the chain so that another class I might not know about can handle it. And if nobody handles it, then object.__init__()
will provide an appropriate error message. Unfortunately, the other answers will not handle that gracefully. They will see **kwargs
and either pass no arguments or pass all of them, which are both incorrect.
The bottom line: There is no general way to discover whether a function call is legal without actually making that function call. inspect
is a crude approximation, and entirely falls apart in the face of variadic functions. Variadic does not mean "pass whatever you like"; it means "the rules are too complex to express in a signature." As a result, while it may be possible in many cases to do what you're trying to do, there will always be situations where there is no correct answer.
Your problem lies with the way you defined your function, it should be defined like this -
def foo(**kwargs):
And then inside the function you can iterate over the number of arguments sent to the function like so -
if kwargs is not None:
for key, value in kwargs.iteritems():
do something
You can find more info about using **kwargs in this post - http://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/
You can also use a decorator function to filter out those keyword arguments that are not allowed in you function. Of you use the signature
function new in 3.3 to return your function Signature
from inspect import signature
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
sig = signature(func)
result = func(*[kwargs[param] for param in sig.parameters])
return result
return wrapper
From Python 3.0 you can use getargspec
which is deprecated since version 3.0
import inspect
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
argspec = inspect.getargspec(func).args
result = func(*[kwargs[param] for param in argspec])
return result
return wrapper
To apply your decorate an existing function you need to pass your function as argument to your decorator:
Demo:
>>> def foo(a, b):
... return a + b
...
>>> foo = decorator(foo)
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> foo(**d)
3
To apply your decorator to a newly function simply use @
>>> @decorator
... def foo(a, b):
... return a + b
...
>>> foo(**d)
3
You can also define your function using arbitrary keywords arguments **kwargs
.
>>> def foo(**kwargs):
... if 'a' in kwargs and 'b' in kwargs:
... return kwargs['a'] + kwargs['b']
...
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> foo(**d)
3
I would do something like this:
def combine(function, dictionary):
return function(**{key:value for key, value in dictionary.items()
if key in inspect.getargspec(function)[0]}
)
Use:
>>> def this(a, b, c=5):
... print(a, b, c)
...
>>> combine(this, {'a': 4, 'b': 6, 'c': 6, 'd': 8})
4 6 6
>>> combine(this, {'a': 6, 'b': 5, 'd': 8})
6 5 5
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With