2

I am testing a function. I would like to know what are the results respect to various parameters.

If the function has only one parameter then it is easy:

def func_a(a):
    return a+1

def test_func(func,parameter_list):    
    return {t:func(t) for t in parameter_list}

print (test_func(func_a,[2,4,5]))

prints

{2: 3, 4: 5, 5: 6}

Here instead I do it with two parameters:

def strategy_2(a,b):
     return a+b

def test_2_strategies(strategy,list_of_lists):
    result={}
    for l1 in list_of_lists[0]:
        for l2 in list_of_lists[1]:
            result[(l1,l2)]=strategy_2(l1,l2)
    return result

print (test_2_strategies(strategy_2,[[1,2,3],[0.3,0.6]]))

and the result is:

{(1, 0.3): 1.3, (1, 0.6): 1.6, (2, 0.3): 2.3, (2, 0.6): 2.6, (3, 0.3): 3.3, (3, 0.6): 3.6}

Perfect.

But what if I wanted to make a similar function where the list of lists could have n lists inside. And test all the combinations.

I looked at decorators, lambda, functools.partial, but I seem to be unable to do it.

I tried for example this:

def func_b(a,b):
    return a+b

def test_func(func,parameter_list):    
    return {t:func(t) for t in parameter_list}

def test_strategy_many_parameters(func,parameter_lists):
    if len (parameter_lists)>1:        
        test_strategy_many_parameters(test_func(lambda x: func(x,y),parameter_lists[0]), parameter_lists[1:])
    else:                              
        return test_func(func,parameter_lists[0])

but it does not work.

I am now looking at this question: Passing functions with arguments to another function in Python?

but I fail to see how to apply it in this case.

I would like something like this:

def func(x1,x2,...,xn):
    result=...
    return result

def test_func(func,list_of_lists):
    ...

print(test_func(func,[[x1min,...,x1max],[x2min,...,x2max],...,[xnmin,...,xnmax]])

and the result would be:

{(x1min, x2min,...,xnmin):func(x1min, x2min,...,xnmin) , ,(x1max, x2max,...,xnmax):func(x1max, x2max,...,xnmax)}

What's an elegant way to do it (or just a way to do it).

Thanks.

Pietro Speroni
  • 3,131
  • 11
  • 44
  • 55

3 Answers3

2

Take a look at itertools.product to generate arguments, and then simply call the function:

arguments = itertools.product(list_of_values_for_arg_1, list_of_values_for_arg_2, ..., list_of_values_for_arg_n)
# or
arguments = itertools.product(*list_of_lists)
for arg in arguments:
    result=func(*arg)

So to generate dict you need to do something like this:

{arg: func(*arg) for arg in itertools.product(*list_of_lists)}
Andrew Morozko
  • 2,576
  • 16
  • 16
  • Thanks. I was just looking at it right now as I realised I needed the cartesian product of the sets. Only problem I don't particularly like having to change all my functions into (*arg) format as it makes them less readable. – Pietro Speroni Jun 16 '18 at 23:58
  • 1
    You don’t need to. If your list of lists contains exactly the number of arguments of your function, then *arg will unpack nicely. For example: `def f(a,b)` can be called like this: `f(*[1,2])`; a is set to 1, b to 2. – Andrew Morozko Jun 17 '18 at 00:03
  • 2
    (the same with tuples) – Attersson Jun 17 '18 at 00:09
  • 1
    If you need to test multiple functions with different number of arguments I can send you code for that in ~12 hours, when I get to my pc. Basic idea is to create dict name_of_argument: list_if_values, then iterate over every function you need to test, get the list of function’s arguments via inspect module, select necessary lists of values, apply product to them and pass resulting arguments to function via **kwargs expansion. – Andrew Morozko Jun 17 '18 at 00:13
  • 1
    Interesting the idea of the inspect module. I did not even know it existed. Thanks. No need for now. But you made me add something else in my tostudy list – Pietro Speroni Jun 17 '18 at 00:33
1

No need for a list of lists, just use a list of tuples (tuples as the function arguments) and it becomes as easy as your base case.

def func(a,b,c):
    result=...
    return result
my_tuple = (1,2,3)


func(*my_tuple)
Attersson
  • 4,755
  • 1
  • 15
  • 29
  • Sorry, I don't get it. I tried v=(1,2) def func(a,b): return a+b func(v) gives an error – Pietro Speroni Jun 16 '18 at 23:47
  • 1
    I edited my answer. Call as above and the tuple will act as arguments. The tuple can be 0..n long (length should match the function arguments number) – Attersson Jun 16 '18 at 23:48
1

If l is your nested list, then map(func, *l) will contain the list of all o/p from the function func with required args and {e:func(*e) for e in zip(*l)} will contain the results in dict format

>>> def func(*args):
>>>    return f'O/P of func: {args}'
>>>
>>> l = [['x1min','x1max'],['x2min','x2max'],['xnmin','xnmax']]
>>> [*map(func, *l)]
["O/P of func: ('x1min', 'x2min', 'xnmin')", "O/P of func: ('x1max', 'x2max', 'xnmax')"]
>>> 
>>> {e:func(*e) for e in zip(*l)}
{('x1min', 'x2min', 'xnmin'): "O/P of func: ('x1min', 'x2min', 'xnmin')", ('x1max', 'x2max', 'xnmax'): "O/P of func: ('x1max', 'x2max', 'xnmax')"}
>>> 
Sunitha
  • 11,777
  • 2
  • 20
  • 23
  • Thanks. There is something that does not convince me. zip usually works with lists that have the same length, but here nothing says that the various subsets have the same length. – Pietro Speroni Jun 17 '18 at 00:22
  • @PietroSperoni.If the subsets are of different lengths, zip can be replaced with `itertools.zip_longest`. I assumed they are all of same lengths, as according to OP, `func` accepts fixed number of n args – Sunitha Jun 17 '18 at 00:29
  • Sorry, but I am not convinced. With zip you make a list taking one from each list. But you keep the order. While here you need to cartesian product. This is why the other solution that use itertool.product work. – Pietro Speroni Jun 17 '18 at 00:36