List of lambda functions does weird things
Question:
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
Answers:
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
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))
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.
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
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
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))
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.