Stored lambda for later use

Hi forum,

When default argument is used on line 4, why the result is different?

The lambda is used on line 6 but does not call it. The lambda is stored for later use. Is this similar to the escape closure in Apple Swift?

Thanks

def fun():
    L = []
    for i in range(1, 4):
        def inner():  # Line 4: (i=i)
            return i * i
        L.append(inner)  # Line 6: escape closure?
    return L

f1, f2, f3 = fun()
print(f1(), f2(), f3())

When you create a nested function that refers to a variable in the enclosing function scope, Python creates a closure.

a = 1

# This does not create a closure.
def f():
    def f_inner():
        print(a)  # Refers to a global
    return f_inner


# But this one does create a closure.
def g():
    a = 1
    def g_inner():
        print(a)  # Refers to a nonlocal variable in enclosing scope
    return g_inner

So g_inner is a closure.

The way closures work is that they grab a link to their environment, the enclosing scope. This is not a copy or a snapshot, it is a live link to the environment.

This means that the reference to a in g_inner will see whatever value a has at the time g_inner is called, not when it was created.

In the above example, a never gets changed, so it doesn’t matter, but if the closure refers to a changing loop variable, by the time the loop ends, all the inner functions will see the same value.

This behaviour is sometimes called late binding. Inside the closure, the value for a is bound to the variable when the function is called.

Sometimes this is the behaviour we want, but if it is not, we can use function parameter defaults to grab a snapshot of the variable.

def h():
    a = 1
    def h_inner(a=a):  # Parameter defaults use *early binding*
        print(a)  # Refers to a local variable
    return h_inner

The h_inner function has its own local variable a, which gets its value at the time the inner function is created (early binding), not when it is called (late binding).

2 Likes

Thanks Steven,

It is very helpful!

In Python, if a variable is assigned to in a function, it is
automatically a local variable. This means we do not need to “declare”
local variables. However, if you do not assign to a name in a function,
its value is sought in the outer scopes (clearly it cannot be local,
since nothing assigns to it).

So when you define this function:

def inner():
    return i * i

the variable i must come from an outer scope, thus the closure - it
comes from the i in the for-loop. Because of the closure it remain
connected to that variable. because you call the function after the loop
has finished, i is 3, the last value from the loop. So each call
computes 9.

When you define the function like this:

def inner(i=i):
    return i * i

i is a local variable (it is defined in the function parameters). Its
default value comes from the value of the for-loop variable i from the
outer scope, so each function definition has the defaults 1, 2 and 3
respectively. When you call them with no arguments, their default value
is used, and it is different for each function you defined. The variable
i within the function is a local variable, and not the variable from
the for-loop.

This local/nonlocal choice is made by static inspection of the function.

Personally, I’d probably try to avoid the confusion of names and use a
different name for the parameter verus the loop variable, something like
this:

def inner(x=i):
    return x * x

Now there is no source of confusion.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like

Thanks Cameron,

It is very clear and helpful

It must be a pitfall that everyone falls at least once. :expressionless:

>>> f = [lambda:i for i in range(3)]
>>> for x in f:
...     x()
...     
2
2
2
>>> def g(i):
...     return lambda:i
...     
>>> f = [g(i) for i in range(3)]
>>> for x in f:
...     x()
...     
0
1
2
1 Like

Hi Kazuya,

Cool!

>>> [v(i) for i, v in enumerate([lambda i: i for i in range(3)])]
[0, 1, 2]
>>>