I have a following simple code:
def get(): return [lambda: i for i in [1, 2, 3]] for f in get(): print(f())
As expected from my python knowledge, output is 3 – entire list will contain last value of
i. But how this works internally?
AFAIK, python variables are simply reference to objects, so first closure must enclose object first
i reference – and this object is definitely 1, not 3 O_O. How it happens that python closure encloses variable itself instead of object this variable reference? Does it save variable name as plain text, some “reference to variable” or what?
Closures don’t refer to variables but rather to scopes. Since the last value of
i in its scope is ‘3’, all three closures return the same. To “lock” the current value of a variable, create a new scope just for it:
def get() : return [ (lambda x: lambda: x)(i) for i in [ 1, 2, 3 ] ] for f in get() : print( f() )
As @thg435 points out, a lambda will not encapsulate the values at that moment, but rather the scope. There are too small ways you can address this:
lambda default argument “hack”
[ lambda v=i: v for i in [ 1, 2, 3 ] ]
Or use functools.partial
from functools import partial [ partial(lambda v: v, i) for i in [ 1, 2, 3 ] ]
Essentially you have to move the scope to be local to the function you are creating. Generally I like using
partial more often since you can pass it a callable, and any args and kargs to create a callable with a proper closure. Internally, it is wrapping your original callable so the scope is shifted for you.
lambda is actually referring to the same
i, which is a variable created by the list comprehension. Upon termination of the list comprehension,
i maintains the value of the final element that it was assigned to until it goes out of scope (which is prevented by encapsulating it within a function and returning it, namely the
lambda). As others have pointed out, closures don’t maintain copies of values, but rather maintain references to variables that were defined within their scope.