10

Code talks more:


from pprint import pprint

li = []

for i in range(5):
        li.append(lambda : pprint(i))

for k in li:
        k()

yield:

4
4
4
4
4

why not

0
1
2
3
4

??

Thanks.

P.S. If I write the complete decorator, it works as expected:



from pprint import pprint

li = []

#for i in range(5):
        #li.append(lambda : pprint(i))

def closure(i):
        def _func():
                pprint(i)
        return _func

for i in range(5):
        li.append(closure(i))

for k in li:
        k()
Grissiom
  • 11,355
  • 3
  • 18
  • 23
  • see [this question](http://stackoverflow.com/q/2295290/195823) and [my answer to this question](http://stackoverflow.com/questions/2295290/what-do-lambda-function-closures-capture-in-python/2295372#2295372) – Adrien Plisson Sep 22 '11 at 11:41
  • You can see that is is closing the variable by moving the original loop into a function, and then calling the function before the `for k in li:` line, so that `i` isn't a valid name in the module level scope. It will still work (and get the same result because it's closing a reference not the value), meaning that the name is closed. – agf Sep 22 '11 at 11:45
  • Thank you all. I think http://stackoverflow.com/questions/2295290/what-do-lambda-function-closures-capture-in-python/2295368#2295368 tells not only the solution but also why. I think my question is duplicate with http://stackoverflow.com/questions/2295290/what-do-lambda-function-closures-capture-in-python – Grissiom Sep 22 '11 at 13:15
  • 1
    Does this answer your question? [What do (lambda) function closures capture?](https://stackoverflow.com/questions/2295290/what-do-lambda-function-closures-capture) – ggorlen May 27 '21 at 15:54

5 Answers5

12

you need to do:

lambda i=i: pprint(i)

instead to capture the current value of i

Dan D.
  • 73,243
  • 15
  • 104
  • 123
3

It does properly reference i, only the thing is, that by the time your list gets populated, i would have the value assigned form the last item in the sequence when the iteration ended, so that's why your seeing 4 all over.

Filip Dupanović
  • 32,650
  • 13
  • 84
  • 114
3

If you don't want to use a default argument -- which could introduce bugs if accidentally called with an argument -- you can use a nested lambda:

from pprint import pprint

li = []

for i in range(5):
    li.append((lambda x: lambda: pprint(x))(i))

for k in li:
    k()

This is an anonymous version of your closure function.

Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
0

The lambda functions create closures over the i variable. After the first for loop is finished, i has value 4.

Then the second for loop starts and all the lambda functions are executed. Each of them then prints the current value of i, which is 4.

sth
  • 222,467
  • 53
  • 283
  • 367
0

The answer: Because the code inside the lambda is using your global variable i.

Your second variant will do the same as the first one with lambda if you remove the parameter i:

def closure():

Instead of

def closure(i):

I.e. the code inside the function will use the global variable i.

warvariuc
  • 57,116
  • 41
  • 173
  • 227
  • It has nothing to do with the scope of the variable, it's the fact that what is closed is a reference to a value rather than a value. See my comment to the question -- if you move the `for i in range` loop into a function, so `i` doesn't exist in global scope, then call the function before the `for k in li` loop, it will give the same answer. – agf Sep 22 '11 at 12:28
  • >if you move the for i in range loop into a function, so i doesn't exist in global scope, then call the function before the for k in li loop, it will give the same answer... --- i.e. non local? That's what i meant. – warvariuc Sep 22 '11 at 18:02
  • it is looking for variable `i`. as there is no function argument `i` it takes variable `i` from outer scope. – warvariuc Sep 22 '11 at 18:04