2

I'm not sure that it's possible, I would like to unpack dictionary into function but I haven't similar parameters. Is it possible to easily restrict a dictionary to a subset?

def foo(a=0, b=0, c=0):
    print("a=%s, b=%s, c=%s"%(a,b,c))

my_dict = {'a':10, 'b':20, 'd':40}
foo(**my_dict)

Output

TypeError                                 Traceback (most recent call last)
<ipython-input-1-d40731664736> in <module>()
      3 
      4 my_dict = {'a':10, 'b':20, 'd':40}
----> 5 foo(**my_dict)

TypeError: foo() got an unexpected keyword argument 'd'

And I want to obtain

a=10, b=20, c=0

result where 'd' is automatically rejected.

It's just an example. In my case the function is not mine, I cannot redefined her parameters, foo is unmodifiable.

I have many functions in similar case as foo() and these functions are object's methods.

Any ideas?

ChrisM
  • 1,576
  • 6
  • 18
  • 29
Yann F.
  • 23
  • 3
  • [Related](https://stackoverflow.com/questions/3420122/filter-dict-to-contain-only-certain-keys) – glibdud Dec 08 '17 at 13:09

2 Answers2

5

Inspecting the function signature will help:

import inspect

params = inspect.signature(foo).parameters.keys()

foo(**{k: v for k, v in my_dict.items() if k in params})
deceze
  • 510,633
  • 85
  • 743
  • 889
  • 1
    Note that `inspect.signature()` is not available pre-Python 3.3. Use [`inspect.getargspec()`](https://docs.python.org/2/library/inspect.html#inspect.getargspec) in 2.x. – zwer Dec 08 '17 at 13:14
  • I thought to it and I hoped an easier solution. Thank you! – Yann F. Dec 08 '17 at 13:23
  • This solution is not correct: Try `def foo(*args): pass` and `my_dict = dict(args=0)`. – Paul Panzer Dec 08 '17 at 13:45
  • I agree that there are more complete solutions possible, depending on what scenarios need to be supported. – deceze Dec 08 '17 at 13:50
  • Well, OP says they have no control over `foo`, so that would make it a better expect anything scenario. – Paul Panzer Dec 08 '17 at 13:58
2

Here is a (hopefully) correct solution using inspect. Since the parameter list returned by inspect may contain star args and also positional only args, it is not enough to just compare the names.

import inspect

def filter_kwds(f, kwds):
    params = inspect.signature(f).parameters
    if any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values()):
        return kwds
    else:
        return {k:kwds[k] for k, v in params.items()
                if k in kwds and v.kind in (inspect.Parameter.KEYWORD_ONLY,
                                            inspect.Parameter.POSITIONAL_OR_KEYWORD)}

def f(a, *b, c=4):
    pass

def g(a, *b, c=4, **d):
    pass

print(filter_kwds(f, dict(a=1, b=2, d=4)))
print(filter_kwds(g, dict(a=1, b=2, d=4)))

Output:

{'a': 1}
{'a': 1, 'b': 2, 'd': 4}

Note that the function in the first example omits the name of the star arg, and in the second example detects the presence of a double star arg and therefore does no filtering

Paul Panzer
  • 51,835
  • 3
  • 54
  • 99