0

Note: This concept is touched upon in Lambda function in list comprehensions, but not to a sufficient depth for my understanding.

We start with an example (in Python) of a closure:

def enclosing_function(free_variable):
    def nested_function(x):
        return x + free_variable
    return nested_function

    closure = enclosing_function(1)  # closure is the "addition by 1" function
    print(closure(37) == 38)  # prints: True

A closure is created by evaluating an enclosing function. The enclosing function contains a nested function, the return value of which contains the (evaluated) free variable. Mathematically, this is equivalent to evaluating a non-empty proper subset of the input variables of a function of more than one variable. In particular, the above example is equivalent to setting y = 1 in the function f(x, y) = x + y.

An alternative implementation is to have the enclosing function return an anonymous function. In this case, the return value is called an anonymous closure:

def enclosing_function(free_variable):
    return lambda x: x + free_variable

We also have anonymous enclosing functions:

print((lambda free_variable: (lambda x: x + free_variable))(1)(37))  # prints: 38

Now, let's set NUMBER_OF_LAMBDAS = 3 and look at two ways of constructing a list of anonymous functions:

a = [lambda x: y for y in range(NUMBER_OF_LAMBDAS)]

b = [lambda x: 0, lambda x: 1, lambda x: 2]

One would naively assume that the lists are the same, but in fact this is not true:

for i in range(NUMBER_OF_LAMBDAS):
    z = a[i]('foo')
    print(f'a_{i}(\'foo\') = {z}')
# Prints:
# a_0('foo') = 2
# a_1('foo') = 2
# a_2('foo') = 2

for i in range(NUMBER_OF_LAMBDAS):
    z = b[i]('bar')
    print(f'b_{i}(\'bar\') = {z}')
# Prints:
# b_0('bar') = 0
# b_1('bar') = 1
# b_2('bar') = 2

The aforementioned StackOverflow article shows that to obtain the expected behavior, one must explicitly define and evaluate an anonymous enclosing function in the list comprehension:

c = [(lambda w: (lambda x: w))(y) for y in range(NUMBER_OF_LAMBDAS)]

for i in range(NUMBER_OF_LAMBDAS):
    z = c[i]('foobar')
    print(f'c_{i}(\'foobar\') = {z}')
# Prints:
# c_0('foobar') = 0
# c_1('foobar') = 1
# c_2('foobar') = 2

a is a list of anonymous closures. c is a list of evalutions of anonymous enclosing functions. Why are these two lists different?

Jon Middleton
  • 363
  • 1
  • 3
  • 8
  • although it's a different language (Javascript), [this question](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example) should be helpful as the semantics of scope and closures are identical between JS and Python. In short, the problem with `a` is that `y` is not actually retrieved until the functions are evaluated, and by then `y` is 2 - what else would it be? – Robin Zigmond Jun 06 '20 at 23:13
  • 1
    The ["Lambda function in list comprehensions"](https://stackoverflow.com/questions/6076270/lambda-function-in-list-comprehensions) question you linked in your question is actually almost entirely unrelated to what you're asking about. I've applied a dupe target that's more relevant. – user2357112 Jun 06 '20 at 23:15
  • I will note, the fact that these are anonymous functions is a red herring, the behavior is exactly the same for any function – juanpa.arrivillaga Jun 07 '20 at 00:10

0 Answers0