4 ms·
I found this example most surprising: >>> def foo(): ... lst = [] ... for i in range(4): ... lst.append(lambda: i)
by leoh 12y ago
I found this example most surprising:
>>> def foo():
... lst = []
... for i in range(4):
... lst.append(lambda: i)
... print([f() for f in lst])
...
>>> foo()
[3, 3, 3, 3]
...So I looked into this a bit more.
It appears that the lambda creates a closure for i, but i is defined in the outer scope and we capture the reference for i, rather than i's value.
So we can get around this:
>>> 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 i is dereferenced when passed to the first lambda constructor, which provides i's dereferenced value as a in a scope provided for the inner lambda. Please correct me if I am wrong, anyone?!
--------Edit 2--------
Out of curiosity, I looked at this in Clojure and the way it creates lambdas is similar to Python (captures reference as opposed to value).
user=> (def a 1)
#'user/a
user=> (def f (fn [] a))
#'user/f
user=> (f)
1
user=> (def a 2)
#'user/a
user=> (f)
2
You could do something like this:
user=> (def f ((fn [x] (fn [] x)) a))
#'user/f
user=> (def a 1)
#'user/a
user=> (def f ((fn [x] (fn [] x)) a))
#'user/f
user=> (f)
1
user=> (def a 10)
#'user/a
user=> (f)
1
But perhaps there is another way as well.
- rhizome31 12y agoYou can capture the index with a keyword argument default value: def foo(): lst = [] for i in range(4): lst.append(lambda i=i: i) print([f() for f in lst]) FWIW I played around with this issue in various languages in this blog post: https://my.smeuh.org/al/blog/lost-in-scope https://my.smeuh.org/al/blog/lost-in-scope Once you get the difference between block scope and function scope, it's quite easy to see what's happening.
- SeanLuke 12y agoCommon Lisp: (setf *a* nil) (dotimes (x 3) (push (lambda () x) *a*)) (mapcar #'funcall *a*) prints (3 3 3 3) as well. I think the author is mixing up variable capture with scoping.