AleMal
AleMal

Reputation: 2087

Python enclosing scope variables with lambda function

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

Answers (3)

Jeff - Mci
Jeff - Mci

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

coredump
coredump

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

chepner
chepner

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

Related Questions