0

I am trying to call a wrapper from SK-learn which passes its **kwargs to an inner function. Unfortunately, one of its positional arguments has the same name as one of the arguments I want to pass in the **kwargs. I can't figure out how to pass them properly.

Example:

# This is a stand-in for a library function. I can't modify its behaviour.
def outer_function(fn, a, **kwargs):
    # do something entirely unrelated with a
    fn(**kwargs)

def expects_parameter_a(a):
    print(a)

def expects_parameter_b(b):
    print(b)

outer_function(expects_parameter_b, a=10, b=20) # this works as expected.
> 20

outer_function(expects_parameter_a, a=10, a=20) # this doesn't work.
outer_function(expects_parameter_a, a=10, kwargs={"a": 20}) # this doesn't work.
outer_function(expects_parameter_a, a=10, **{"a": 20}) # this doesn't work.
JAD
  • 2,035
  • 4
  • 21
  • 35
  • "I am trying to call a wrapper from SK-learn" - what wrapper is this? – user2357112 May 12 '20 at 07:47
  • How does your code know whether it needs to pass on ``a``? It would be incorrect to pass on ``a`` to ``expects_parameter_b``. Does ``outer_function`` use the kwargs at all? Why does ``outer_function`` do something *entirely unrelated*? – MisterMiyagi May 12 '20 at 07:50
  • @user2357112supportsMonica specifically `GridSearchCV.fit` which has a `groups` argument, while my inner model also needs a `groups` argument in its `fit` method. – JAD May 12 '20 at 07:52
  • @MisterMiyagi it's not entirely unrelated. It just uses the argument and does not pass it into the inner method. It's not really relevant for the question I don't think. It just matters that the argument exists. – JAD May 12 '20 at 07:55

3 Answers3

2

Don't make those parameters "first level" arguments; instead, accept a dict of arguments you're going to pass on to fn:

def outer_function(fn, a, fn_kwargs):
    # do something entirely unrelated with a
    fn(**fn_kwargs)

outer_function(expects_parameter_a, a=10, fn_kwargs={"a": 20})

This could really be simplified and generalised to this:

from functools import partial

def outer_function(fn, a):
    ...
    fn()

outer_function(partial(expects_parameter_a, a=20))
# or:
outer_function(lambda: expects_parameter_a(a=20))

In other words, don't let outer_function worry about passing on the parameters at all, simply pass in a callable that already has all necessary parameters bound to it.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • Sadly the `outer_function` is from a library, so its API is out of my control. The simplified suggestion is very useful though. Thanks a lot. – JAD May 12 '20 at 07:50
2

Pass the arguments to the inner function using functools.partial, and pass a to outer_function separately:

outer_function(partial(expects_parameter_b, b=20), a=10)
outer_function(partial(expects_parameter_a, a=10), a=20)

This avoids passing arguments from outer_function to the inner function, and thus circumvents any conflicts. Note that outer_function does not need to be modified for this to work.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
-2

You can try something like this

def f1(*args, **kwargs):
    print("args   : ", args)
    print("kwargs : ", kwargs)

f1()
f1(1, 2)
f1(a=100, b=200)
f1(1, a=100)
f1(1, 2, a=100, b=200)

Output...

args   :  ()
kwargs :  {}
args   :  (1, 2)
kwargs :  {}
args   :  ()
kwargs :  {'a': 100, 'b': 200}
args   :  (1,)
kwargs :  {'a': 100}
args   :  (1, 2)
kwargs :  {'a': 100, 'b': 200}
anbocode
  • 53
  • 6