8

I was slightly surprised by this example given by Eli Bendersky (http://eli.thegreenplace.net/2015/the-scope-of-index-variables-in-pythons-for-loops/)

>>> def foo():
...     lst = []
...     for i in range(4):
...         lst.append(lambda: i)
...     print([f() for f in lst])
...
>>> foo()
[3, 3, 3, 3]

But when I thought about it, it made some sense — the lambda is capturing a reference to i rather than i's value.

So a way to get around this is the following:

>>> def foo():
...     lst = []
...     for i in range(4):
...         lst.append((lambda a: lambda: a)(i))
...     print([f() for f in lst])
...
>>> foo()
[0, 1, 2, 3]

It appears that the reason that this works is that when i is provided to the outer lambda, the outer lambda creates a scope and dereferences i, setting a to i. Then, the inner lambda, which is returned, holds a reference to a.

Is this a correct explanation?

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
abhillman
  • 3,942
  • 1
  • 20
  • 21
  • Worth reading: http://stackoverflow.com/questions/20246523/how-references-to-variables-are-resolved-in-python/20250802#20250802 and http://stackoverflow.com/a/14414638/1252759 – Jon Clements Jan 18 '15 at 21:46

3 Answers3

13

Default param is an another way to catch a value:

lst.append(lambda i=i: i)
bav
  • 1,543
  • 13
  • 13
3

It appears that the reason that this works is that when i is provided to the outer lambda, the outer lambda creates a scope and dereferences i, setting a to i. Then, the inner lambda, which is returned, holds a reference to a.

Is this a correct explanation?

I don't like it. Python does not pass by reference:

def func(x):
    x = 10

num = 3
func(num)

print num  #=>3

As a result, the terms reference and dereference are not in the python lexicon. Or, you could say that python always dereferences a function argument before assigning it to a parameter variable--so your explanation doesn't really explain anything.

The reason the example works is because of the rule:

A function's local variables are destroyed after it finishes executing.

A function's local variables include its parameter variables. Every time the outer lambda executes, a new 'a' variable is created. As a result, each inner lambda closes over a different 'a' variable.

You did allude to that state of affairs:

the outer lambda creates a scope

...

the lambda is capturing a reference to i rather than i's value.

Or, as I like to phrase it.

A closure closes over variables--not values.

That is the way closures work in most languages(an exception being perl, where closures close over values).

7stud
  • 46,922
  • 14
  • 101
  • 127
0

Yes, it looks correct. If you are familiar with javascript and know closures, you will notice how similar they are.

If not - there is a nice explanation on SO regarding JS closures and the concept is absolutely the same (as well as the explanation and even wrong and correct usage).

Community
  • 1
  • 1
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753