0

This is so weird, if I make a list of lambda functions and with a for, I print the output of each function, everything works, but if I individually print the output of a single function like the first one, it gives me the output of the last function or I don't even know what it gives me, for example:

X = [1,2,3,4]
L = []

for i in range(len(X)):
   L.append(lambda x: X[i]**x)

for i in range(len(X)):
   print(L[i](2))

This gives me:

1
4
9
16

That is correct, but if i want only the first one:

print(L[0](2))
# -> 16

And if I want the second one does the same, and so on, I checked the lambda functions were all different and they are. I don't know what's going on

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Tito Diego
  • 47
  • 1
  • 6
  • With the lambdas you're delaying evaluation of `i` until after the loop has finished, so you're always getting its final value, *unless* you're running it in another scope (e.g. another `for i in...` loop) that's giving `i` different values. – Samwise Jul 30 '21 at 17:35
  • Instead of ```for i in range(len(X))``` and ```X[i]**x```, do ```for i in X```, ```L.append(lambda x: i**x)``` –  Jul 30 '21 at 17:37
  • Related: [How do I create a list of Python lambdas (in a list comprehension/for loop)?](https://stackoverflow.com/q/452610/4518341) -- it's almost the same issue except that you're reusing the free variable name. – wjandrea Jul 30 '21 at 17:49
  • BTW, welcome to Stack Overflow! Check out the [tour], and [ask] if you want tips. – wjandrea Jul 30 '21 at 17:55
  • The final loop in your first example only *incidentally* works because it *happens* to use the same name `i` for the iteration variable. If it were `j` instead (or if you iterated directly over the list rather than over indices), then `i` would still have its value from after the first loop. – Karl Knechtel Aug 16 '22 at 00:48

3 Answers3

2

The lambda references the global variable i, so after the for loop, i==3, computing X[3]**2:

X = [1,2,3,4]
L = []

for i in range(len(X)):
   L.append(lambda x: X[i]**x)

for f in L:
    print(f(2))

Output:

16
16
16
16

A way to fix is to capture the current value of global i as a local parameter i when the function is defined:

X = [1,2,3,4]
L = []

for i in range(len(X)):
   L.append(lambda x, i=i: X[i]**x)  # capture i as a parameter

for f in L:
    print(f(2))

Output:

1
4
9
16
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
0

You are expecting the value of i to be part of the function, not the name. That's not how function definitions work, either with def statements or lambda expressions.

I'd recommend defining a function maker, so that your expected function can close over the local value of i.

def make_function(i):
    return lambda x: X[i]*x*x

Now i refers not to a global variable i, but to a local variable i in the function call. Every call to make_function creates a different local variable initialized with the value of the argument, and that variable is what your function will refer to when it is called.

for i in range(len(X)):
   L.append(make_function(i))
chepner
  • 497,756
  • 71
  • 530
  • 681
0

Your lambda closes around the variable i. Despite what the for loops make it look like, there's actually only one variable i in your entire code, so when you call L[i](2) or L[0](2), you're using the current value of i. Let's look at your first example.

for i in range(len(X)):
    print(L[i](2))

Here, we call L[i] with the enclosing value of i, so this is really basically

for i in range(len(X)):
    print(X[i] ** 2)

On the other hand, in your second example

print(L[0](2))

The i value is the i from the final loop iteration above. To get around this, you need to explicitly capture the current value of i.

L.append(lambda x, i=i: X[i]**x)

This exploits one of Python's least intuitive features to explicitly capture a value in a lambda.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • 1
    Integers aren't mutable... Or is there another aspect to that that I'm missing? – wjandrea Jul 30 '21 at 17:50
  • 1
    It's the same trick though. In most languages with default arguments, the default argument is evaluated when the function is called. In Python, default arguments are evaluated once when the function is defined and stored as data on the function object. The same trick that causes the mutable default argument quirk also allows us to do this. – Silvio Mayolo Jul 30 '21 at 17:53