3

I am struggling to pass a list of functions with a list of corresponding parameters. I also checked here, but it wasn't very helpful. for example (a naive approach which doesn't work):

def foo(data, functions_list, **kwarg):
    for func_i in functions_list:
        print func_i(data, **kwarg)

def func_1(data, par_1):
    return some_function_1(data, par_1)

def func_2(data, par_2_0, par_2_1):
    return some_function_2(data, par_2_0, par_2_1)

foo(data, [func_1, func_2], par_1='some_par', par_2_0=5, par_2_1=11)

Importantly, par_1 cannot be used in func_2, so each function consumes a unique set of parameters.

cs95
  • 379,657
  • 97
  • 704
  • 746
Arnold Klein
  • 2,956
  • 10
  • 31
  • 60
  • 1
    Each function takes only one argument or many? – ettanany Sep 13 '17 at 10:55
  • @ettanany, great question (see updated question). Any number (a list) of parameters. – Arnold Klein Sep 13 '17 at 10:56
  • you can make decorators of your own.. – Amrit Sep 13 '17 at 10:56
  • 1
    You have a typo in the code supplied. Line 1 you use **kwarg, line 3 you use **kwargs (note the addition of an 's' on line 3). – wolfson109 Sep 13 '17 at 10:56
  • 2
    Maybe I'm misunderstanding something, but this looks like a _terrible_ idea to me. Why not properly pair the functions with their parameters, for example with `(function, args, kwargs)` tuples like `foo(data, [(func1, (), {"par_1": "some_par"}), (func2, (5, 11), {})])`? – Aran-Fey Sep 13 '17 at 11:21
  • 6
    Can you give some background for what you are attempting to do? Right now, this only appears to attract gimmick-y solutions that may solve your specific requirement but will otherwise not improve your code quality because the initial situation is already odd. I don’t see why creating a wrapper function here wouldn’t work for example (e.g. `foo(data, foo_it(func_1, 'some_par'), foo_it(func_2, 5, 11))`). Where do those values come from, what are you trying to do? – poke Sep 13 '17 at 12:20
  • @poke, I am writing a template function, which consumes two (or more) functions, each with their 'personal', non-overlapping parameters. I have an iterator over the functions, so this is why I preferred to pass function as a list. I am not a prof. programmer (i'm a scientist who write programs for calculatiions), so you're probably right, that my design is not optimal. Thank you for your suggestion, I will re-think my approach. – Arnold Klein Sep 13 '17 at 12:55

7 Answers7

3

You could use the function's name as the keyword arguments. When indexing kwargs, you'd use func_i.__name__ as the key.

def foo(data, function_list, **kwargs):
    for func_i in function_list:
        print(func_i(data, kwargs[func_i.__name__]))

And now,

foo(data, [func_1, func_2], func_1='some_par', func_2=[5, 11])
cs95
  • 379,657
  • 97
  • 704
  • 746
  • @ArnoldKlein Tried it with some dummy functions and this appears to work. But if you have multiple arguments for a function, please pass an iterable. – cs95 Sep 13 '17 at 11:02
  • 1
    @ArnoldKlein Also, note a couple of typos: `function_list/function_list`, `kwargs/kwarg`... make sure those are fixed. – cs95 Sep 13 '17 at 11:02
  • The problem with this method is that it does bring some extra work in case of several arguments shared with some but not all functions. The @Strinnityk solution is better in that regard. – Nicolas David Sep 13 '17 at 11:49
  • @NicolasDavid Yes but in addition to that flexibility, it imposes a tonne of constraints, such as variable names, and so on. – cs95 Sep 13 '17 at 11:50
  • yup, i agree that it depends of the context. But with your method **(possible cons 1)** If there is a lot of common variables, you have to re-write them for each function, which **may** end up being longer than writing their name once. **(possible cons 2)** If the number of function is big, writing their names twice is boring, IMO. **(possible pro1)** In case of few functions, and few arguments, it's nice, but then I think this signature `foo(data, {func_name:[arg,arg,...],...})` is even more lazy – Nicolas David Sep 13 '17 at 12:05
  • @NicolasDavid You're nitpicking :p – cs95 Sep 13 '17 at 12:07
3

You could use inspect.getargspec (I assume you use Python 2, you shouldn't use that function in Python 3 because it has been deprecated) to find out which argument names a function has and build a new dictionary based on those:

import inspect

def foo(data, functions_list, **kwargs):
    for func_i in functions_list:
        newkwargs = {name: kwargs[name] 
                     for name in inspect.getargspec(func_i).args 
                     if name in kwargs}
        print(func_i(data, **newkwargs))

def func_1(data, par_1):
    return data, par_1

def func_2(data, par_2_0, par_2_1):
    return data, par_2_0, par_2_1

>>> data = 10
>>> foo(data, [func_1, func_2], par_1='some_par', par_2_0=5, par_2_1=11)
(10, 'some_par')
(10, 5, 11)

But a better way would be to simply associate parameters with functions that doesn't rely on introspection.

MSeifert
  • 145,886
  • 38
  • 333
  • 352
2

If you want to keep the foo function with that exact same declaration and you don't mind each function receiving the whole set of parameters you could do it like this:

You just need to add to each 'my_*' function the **kwargs parameter.

def foo(data, functions_list, **kwargs):
    for my_function in functions_list:
        print(my_function(data, **kwargs))


def my_sum(a, b, **kwargs):
    return a + b


def my_sub(a, c, **kwargs):
    return a - c


foo(0, [my_sum, my_sub], b=3, c=10)

Python automatically parses kwargs setting the b and c parameters where it has the value.

Strinnityk
  • 548
  • 1
  • 5
  • 17
  • 2
    in fact removing data from foo signature wood be even more coherent, it would then gives `foo([my_sum,my_sub], a=data, b=3, c=10)` – Nicolas David Sep 13 '17 at 11:51
1

An approach would be making the 3rd argument of foo a positional argument and pass in a list of args with functions list:

def foo(data, functions_list, args):
    for func, arg in zip(functions_list, args):
        print(func(data, arg))


def func1(data, par_1):
    return 'func1 called with {}'.format(par_1)


def func2(data, par_2):
    return 'func2 called with {}'.format(par_2)

foo('some_data', [func1, func2],
    [
        {'par_1_1': 11, 'par_1_2': 12},
        {'par_2_1': 21, 'par_2_2': 22}
    ])

zip() is used to map each function with the corresponding args.

Output:

func1 called with {'par_1_1': 11, 'par_1_2': 12}
func2 called with {'par_2_1': 21, 'par_2_2': 22}
ettanany
  • 19,038
  • 9
  • 47
  • 63
1

Another approach can be like this:

def foo(data, function_list, **kwargs):
    function_dict = {
        "func_1": func_1,
        "func_2": func_2        
    }
    for func_i in function_list:
        print function_dict[func_i](data, **kwargs)

def func_1(data, **arg):
    filtered_argument = {key: value for key, value in arg.items() if key.startswith('par_1')}
    return list([data, filtered_argument])

def func_2(data, **arg):
    filtered_argument = {key: value for key, value in arg.items() if key.startswith('par_2_')}
    return list([data, filtered_argument])


data = [1,2,3]  
foo(data, ['func_1', 'func_2'], par_1='some_par', par_2_0=5, par_2_1=11)

Output:

[[1, 2, 3], {'par_1': 'some_par'}]
[[1, 2, 3], {'par_2_0': 5, 'par_2_1': 11}]

I am sure that you can improvise your current code as it gets ugly in this way.

arshovon
  • 13,270
  • 9
  • 51
  • 69
1

I like @COLDSPEED's approach, but want to present yet another solution. Pass always 3 values: function, args, keyword args:

Usage:

foo(
    func_1, ('some_par',), {},
    func_2, (5, 11), {},
)

Implementation (Python3 syntax):

def foo(*args3):
    while args3:
        func, args, kwargs, *args3 = args3
        func(*args, **kwargs)
VPfB
  • 14,927
  • 6
  • 41
  • 75
1

You can do it something like that, "close" each parameters for function in a list item and then let "foo" split it backwards:

def foo(data, functions_list, kwarg):
    for func_i, args in zip(functions_list, kwarg):
        func_i(data, **args)


def func_1(data, par_1):
    print("func_1 %s %s" % (data, par_1))


def func_2(data, par_2_0, par_2_1):
    print("func_2 %s "
          "%s %s" % (data, par_2_0, par_2_1))

data = "Some Data"

foo(data, [func_1, func_2], [{"par_1":'some_par'}, {"par_2_0":5, "par_2_1":11}])
Roee Gavirel
  • 18,955
  • 12
  • 67
  • 94