Reputation: 2087
I wrote this simple code:
def makelist():
L = []
for i in range(5):
L.append(lambda x: i**x)
return L
ok, now I call
mylist = makelist()
because the enclosing scope variable is looked up when the nested functions are later called, they all effectively remember the same value: because of this, I expected to find the value the loop variable had on the last loop iteration, but when I check my list I see:
>>> mylist[0](0)
1
>>> mylist[0](1)
4
>>> mylist[0](2)
16
>>>
I'm so confused, why my code doesn't retain the last for loop values? Why I don't have to explicitly retain enclosing scope values with default arguments like this:
L.append(lambda x, i=i: i ** x)
Thanks in advance
Upvotes: 6
Views: 1750
Reputation: 1013
I'll try my best To break it down a bit in more simple-terms
1st -
The for loop is looping and appending the 'uncalled' lambda function to the list. --in the case of your example, it will do this 5 times.
2nd
Now.. AFTER the for loop completes it's iterations..the value for "i" in the for loop will be "4". --the lambda function does not contain any value for "i" because you haven't called it, yet.
3rd
When you call the lambda function , only then will the function look to 'remember' the value of variable "i" when the loop completed it's iterations; which is "4"
4th
Lambda then inserts "4" as an argument to make it's calculations.
Upvotes: 0
Reputation: 38809
Even though i
takes multiple values over time, there is effectively only one variable, i
. The content of i
is being changed during the loop. But the closures captures variables, not values. Nothing is evaluated inside the lambda until you call it. At the time you call the function, you access the current value of i
, which happens to be the last one.
As for why i=i
solves the problem, this is explained for example in The Hitchhiker's guide to Python (Common Gotchas):
Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.
And so, each fresh binding that occurs inside the closure you create (and happen to be named i
just like the one outside) has its default value being computed when creating the closure. Consequently, you have the "right" value in place, ready to be used when the closure is called.
Upvotes: 5
Reputation: 531125
First, observe that all 5 functions in the list are identical, because they all use the final value of i
from inside makelist
.
i
, though, is part of the closure created when the lambda
expressions were evaluated. That means that if you assign a new value to i
in the scope where you call a function from makelist
, this does not affect any of the functions. They look up the value of i
from the namespace attached to the function as a result of the closure, not from the global scope.
Upvotes: 3