3

I have a list of functions each of these takes a parameter.

I want to use a function of a library which takes functions but expects them to have no parameters.

So I want to create a list of new functions using lambda to pass the param "externally"

However the new list of functions doesn't yield the expected result.

(This is some minimal example)

def fun_a(param):
    print("a")
    
def fun_b(param):
    print("b")
    

def do_something(funs):
    funs_out = []
    for fun in funs:
        funs_out.append(lambda: fun(0))
        
    return funs_out
        
   
funs = [fun_a,fun_b]

funs[0](0) # prints a
funs[1](0) # prints b

funs_changed = do_something(funs)
#funs_changed = [lambda: f(0) for f in funs]
    
funs_changed[0]() # prints b ??? expected a
funs_changed[1]() # prints b

I tried funs_changed = [lambda: f(0) for f in funs] before as it seems more pythonic, and then tried to use more explicit code (raw for loop) to find the root cause but without success.

What do I miss?

Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
FailX
  • 33
  • 6

2 Answers2

2

just use value hack:

funs_changed = [lambda f=f: f(0) for f in funs]

it would be OK and produce an output of:

a
b
a
b

Also there is a solution for your initial variant of the code: just patch do_something function if you prefer keep it as function to avoid in-line lamda-list comprehention in main code contex (we'll do it in function):

def do_something(funs):
    funs_out = [lambda f=f: f(0) for f in funs]
    return funs_out
Jocker
  • 368
  • 1
  • 7
  • 1
    but why the other option doesnt work? – adir abargil Nov 23 '20 at 12:25
  • 1
    could you elaborate your answer or can you point me to some explanation? I want to understand your solution/my mistake. – FailX Nov 23 '20 at 12:27
  • 2
    Yes. Please look for further info here: https://docs.python.org/3.9/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result – Jocker Nov 23 '20 at 12:31
2

You can use functools.partial:

from functools import partial


def fun_a(param):
    print("a")
    
def fun_b(param):
    print("b")
    

def do_something(funs):
    funs_out = []
    for fun in funs:
        funs_out.append(partial(fun, 0))
        
    return funs_out
        
   
funs = [fun_a, fun_b]

funs[0](0)   # prints a
funs[1](0)   # prints b

funs_changed = do_something(funs)
# funs_changed = [partial(fun, 0) for f in funs]
    
funs_changed[0]() # prints a
funs_changed[1]() # prints b

From this answer:

Roughly, partial does something like this (apart from keyword args support etc):

def partial(func, *part_args):
    def wrapper(*extra_args):
        args = list(part_args)
        args.extend(extra_args)
        return func(*args)

    return wrapper
Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
  • it doesnt explain why the example in the question does not work? – adir abargil Nov 23 '20 at 12:29
  • Can you give a statement about @Jocker 's answer? which solution should I use, which one is more understandable/more pythonic? – FailX Nov 23 '20 at 12:38
  • 1
    For this use case, I find `functools.partial` easier to understand than `lambda`, which, as you discovered was a bit tricky to pin down. It is especially true when you are reading other people's code. In many other cases, `lambdas` are perfectly fine... but that is only my opinion, maybe others will disagree. – Reblochon Masque Nov 23 '20 at 12:45