14

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?

3 Answers 3

14

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.

Sign up to request clarification or add additional context in comments.

3 Comments

+1 For using partial here, I think it's a much cleaner approach. You might want to edit your code though, spaces inside list brackets are bad according to PEP-8. (I know you were following the asker, I edited it there).
@Lattyware: Thanks! lambdas are cool but I always find them more messy to read. partial just feels so much more readable and portable.
100% agree, I try to avoid lambdas wherever I can, and this is a prime case where partial is both more clear and simpler.
11

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() )

9 Comments

Is it some additional information available on the subject so i can see what hidden variables are used to "save" scope, access it, etc?
(lambda x: lambda : x)(i) is one of the ugliest things I've seen done in Python. Ick. (Not that your answer is wrong or bad, per-se, just saying - it's hard to read).
@EyeofHell: I think pep-227 is the canonical document about python scoping rules. Also, there are some good answers on this here on SO, e.g. here
@Lattyware: de gustibus non est disputandum.
@GregE. I don't think it's impossible to do nicely in Python. I think jdi's answer of functools.partial() and lambda mixed is the nicest solution, it describes what is being done in a nicer way - from my point of view.
|
4

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.