What do lambda function closures capture?
Rise to the top 3% as a developer or hire one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------
Music by Eric Matyas
https://www.soundimage.org
Track title: Puzzle Game 2 Looping
--
Chapters
00:00 What Do Lambda Function Closures Capture?
02:23 Accepted Answer Score 233
03:39 Answer 2 Score 330
03:59 Answer 3 Score 35
04:36 Answer 4 Score 55
04:54 Thank you
--
Full question
https://stackoverflow.com/questions/2295...
--
Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...
--
Tags
#python #lambda #closures
#avk47
ANSWER 1
Score 330
you may force the capture of a variable using an argument with a default value:
>>> for i in [0,1,2,3]:
... adders[i]=lambda a,i=i: i+a # note the dummy parameter with a default value
...
>>> print( adders[1](3) )
4
the idea is to declare a parameter (cleverly named i) and give it a default value of the variable you want to capture (the value of i)
ACCEPTED ANSWER
Score 233
What do the closures capture exactly?
Closures in Python use lexical scoping: they remember the name and scope of the closed-over variable where it is created. However, they are still late binding: the name is looked up when the code in the closure is used, not when the closure is created. Since all the functions in your example are created in the same scope and use the same variable name, they always refer to the same variable.
There are at least two ways to get early binding instead:
The most concise, but not strictly equivalent way is the one recommended by Adrien Plisson. Create a lambda with an extra argument, and set the extra argument's default value to the object you want preserved.
More verbosely but also more robustly, we can create a new scope for each created lambda:
>>> adders = [0,1,2,3] >>> for i in [0,1,2,3]: ... adders[i] = (lambda b: lambda a: b + a)(i) ... >>> adders[1](3) 4 >>> adders[2](3) 5The scope here is created using a new function (another lambda, for brevity), which binds its argument, and passing the value you want to bind as the argument. In real code, though, you most likely will have an ordinary function instead of the lambda to create the new scope:
def createAdder(x): return lambda y: y + x adders = [createAdder(i) for i in range(4)]
ANSWER 3
Score 55
For completeness another answer to your second question: You could use partial in the functools module.
With importing add from operator as Chris Lutz proposed the example becomes:
from functools import partial
from operator import add # add(a, b) -- Same as a + b.
adders = [0,1,2,3]
for i in [0,1,2,3]:
# store callable object with first argument given as (current) i
adders[i] = partial(add, i)
print adders[1](3)
ANSWER 4
Score 35
Consider the following code:
x = "foo"
def print_x():
print x
x = "bar"
print_x() # Outputs "bar"
I think most people won't find this confusing at all. It is the expected behaviour.
So, why do people think it would be different when it is done in a loop? I know I did that mistake myself, but I don't know why. It is the loop? Or perhaps the lambda?
After all, the loop is just a shorter version of:
adders= [0,1,2,3]
i = 0
adders[i] = lambda a: i+a
i = 1
adders[i] = lambda a: i+a
i = 2
adders[i] = lambda a: i+a
i = 3
adders[i] = lambda a: i+a