Recently I started playing around with Python and I came around something peculiar in the way closures work. Consider the following code:
adders = [None, None, None, None]
for i in [0, 1, 2, 3]:
adders[i] = lambda a: i+a
print adders[1](3)
It builds a simple array of functions that take a single input and return that input added by a number. The functions are constructed in for loop where the iterator i runs from 0 to 3. For each of these numbers a lambda function is created which captures i and adds it to the function's input. The last line calls the second lambda function with 3 as a parameter. To my surprise the output was 6.
I expected a 4. My reasoning was: in Python everything is an object and thus every variable is essential a pointer to it. When creating the lambda closures for i, I expected it to store a pointer to the integer object currently pointed to by i. That means that when i assigned a new integer object it shouldn't effect the previously created closures. Sadly, inspecting the adders array within a debugger shows that it does. All lambda functions refer to the last value of i, 3, which results in adders[1](3) returning 6.
Which make me wonder about the following:
- What do the closures capture exactly?
- What is the most elegant way to convince the
lambdafunctions to capture the current value ofiin a way that will not be affected whenichanges its value?
For a more accessible, practical version of the question, specific to the case where a loop (or list comprehension, generator expression etc.) is used, see Creating functions (or lambdas) in a loop (or comprehension). This question is focused on understanding the underlying behaviour of the code in Python.
If you got here trying to fix a problem with making buttons in Tkinter, try tkinter creating buttons in for loop passing command arguments for more specific advice.
See What exactly is contained within a obj.__closure__? for technical details of how Python implements closures. See What is the difference between Early and Late Binding? for related terminology discussion.
ileave the namespace?print iwouldn't work after the loop. But I tested it for myself and now I see what you mean - it does work. I had no idea that loop variables lingered after the loop body in python.