1

I have a tuple of functions that I want to pre-load with some data. Currently the way I am doing this is below. Essentially, I make a list of the new functions, and add the lambda functions to it one at a time, then reconvert to a tuple. However, when I use these functions in a different part of the code, every one of them acts as if it were the last one in the list.

def newfuncs(data, funcs):
    newfuncs = []
    for f in funcs:
        newf = lambda x: f(x, data)
        newfuncs.append(newf)
    return tuple(newfuncs)

Here is a simple example of the problem

funcs = (lambda x, y: x + y, lambda a, b: a - b)
funcs = newfuncs(10, funcs)
print(funcs[0](5))
print(funcs[1](5))

I would expect the number 15 to be printed, then -5. However, this code prints the number -5 twice. If anyone can help my understand why this is happening, it would be greatly appreciated. Thanks!

2 Answers 2

3

As mentioned, the issue is with the variable f, which is the same variable assigned to all lambda functions, so at the end of the loop, every lambda sees the same f.

The solution here is to either use functools.partial, or create a scoped default argument for the lambda:

def newfuncs(data, funcs):
    newfuncs = []
    for f in funcs:
        newf = lambda x, f=f: f(x, data)   # note the f=f bit here
        newfuncs.append(newf)
    return tuple(newfuncs) 

Calling these lambdas as before now gives:

15
-5

If you're using python3.x, make sure to take a look at this comment by ShadowRanger as a possible safety feature to the scoped default arg approach.

Sign up to request clarification or add additional context in comments.

1 Comment

Side-note: If you're on Python 3, and you're using the scoped default argument approach, it's a good idea to make such arguments keyword-only, so passing extra positional parameters by accident doesn't silently overwrite the bound values (you could only change them by explicitly passing the name, f in this case, as a keyword argument). Only difference is changing lambda x, f=f: to lambda x, *, f=f:, where the * (with no name) makes all arguments after it keyword-only.
2

This is a well-known Python "issue," or should I say "this is just the way Python is."

You created the tuple:

( x => f(x, data), x => f(x, data) )

But what is f? f is not evaluated until you finally call the functions!

First, f was (x, y)=>x+y. Then in your for-loop, f was reassigned to (x, y)=>x-y.

When you finally get around to calling your functions, then, and only then, will the value of f be looked up. What is the value of f at this point? The value is (x, y)=>x-y for all of your functions. All of your functions do subtraction. This is because f is reassigned. There is ONLY ONE f. And the value of that one and only one f is set to the subtraction function, before any of your lambdas ever get called.

ADDENDUM

In case anyone is interested, different languages approach this problem in different ways. JavaScript is rather interesting (some would say confusing) here because it does things the Python way, which the OP found unexpected, as well as a different way, which the OP would have expected. In JavaScript:

> let funcs = []
> for (let f of [(x,y)=>x+y, (x,y)=>x-y]) {
    funcs.push(x=>f(x,10));
  }
> funcs[0](5)
15
funcs[1](5)
-5

However, if you change let to var above, it behaves like Python and you get -5 for both! This is because with let, you get a different f for each iteration of the for-loop; with var, the whole function shares the same f, which keeps getting reassigned. This is how Python works.

cᴏʟᴅsᴘᴇᴇᴅ has shown you that the way to do what you expect is to make sure you get that different f for each iteration, which Python allows you do in a pretty neat way, defaulting the second argument of the lambda to a f local to that iteration. It's pretty cool, so their answer should be accepted if it helps you.

1 Comment

The javascript sample is a nice touch, thanks for adding it in!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.