Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 748
AleMal Avatar asked Dec 06 '16 14:12

AleMal


1 Answers

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.

like image 198
coredump Avatar answered Nov 15 '22 04:11

coredump