Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What do lambda function closures capture?

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 lambda functions to capture the current value of i in a way that will not be affected when i changes its value?
like image 350
Boaz Avatar asked Feb 19 '10 09:02

Boaz


People also ask

What is closure in lambda expression?

"A closure is a lambda expression paired with an environment that binds each of its free variables to a value. In Java, lambda expressions will be implemented by means of closures, so the two terms have come to be used interchangeably in the community."

Why a lambda expression forms a closure?

a function that can be treated as an object is just a delegate. What makes a lambda a closure is that it captures its outer variables. lambda expressions converted to expression trees also have closure semantics, interestingly enough.

What are lambda captures?

Captures default to const value. By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.

What is __ closure __ in Python?

A closure is a function object that remembers values in enclosing scopes even if they are not present in memory. The __closure__ attribute of a closure function returns a tuple of cell objects. This cell object also has an attribute called cell_contents, which returns returns the contents of the cell.


2 Answers

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)

like image 194
Adrien Plisson Avatar answered Sep 27 '22 18:09

Adrien Plisson


Your second question has been answered, but as for your first:

what does the closure capture exactly?

Scoping in Python is dynamic and lexical. A closure will always remember the name and scope of the variable, not the object it's pointing to. 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.

Regarding your other question of how to overcome this, there are two ways that come to mind:

  1. 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.

  2. A little more verbose but less hacky would be to create a new scope each time you create the 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)  5 

The scope here is created using a new function (a 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)] 
like image 32
Max Shawabkeh Avatar answered Sep 27 '22 16:09

Max Shawabkeh