6

Today I explored a weird behavior of Python. An example:

closures = []
for x in [1, 2, 3]:
    # store `x' in a "new" local variable
    var = x

    # store a closure which returns the value of `var'
    closures.append(lambda: var)

for c in closures:
    print(c())

The above code prints

3
3
3

But I want it to print

1
2
3

I explain this behavior for myself that var is always the same local variable (and python does not create a new one like in other languages). How can I fix the above code, so that each closure will return another value?

Stephan Kulla
  • 4,739
  • 3
  • 26
  • 35
  • Hint: what introduces a new variable scope in Python? –  Jul 06 '12 at 16:49
  • just saw that http://stackoverflow.com/questions/7546285/creating-lambda-inside-a-loop?rq=1 is a duplicate question. how can I close this one or shall I delete it? – Stephan Kulla Jul 06 '12 at 16:49
  • @tampis: Just vote to close – the moderators will eventually decide whether this question is merged or deleted. – Sven Marnach Jul 06 '12 at 16:51
  • Actually, I don't think that that even does what you think it does. Try adding `del var` after the first loop. – cha0site Jul 06 '12 at 16:53
  • @cha0site `del var` gives the exception `lobal name 'var' is not defined` – Stephan Kulla Jul 06 '12 at 16:56
  • @tampis: Exactly. Note that it throws that exception not on the `del var` line, but on the `print(c())` line. That is, the code didn't actually create a closure. – cha0site Jul 06 '12 at 17:04

3 Answers3

11

The easiest way to do this is to use a default argument for your lambda, this way the current value of x is bound as the default argument of the function, instead of var being looked up in a containing scope on each call:

closures = []
for x in [1, 2, 3]:
    closures.append(lambda var=x: var)

for c in closures:
    print(c())

Alternatively you can create a closure (what you have is not a closure, since each function is created in the global scope):

make_closure = lambda var: lambda: var
closures = []
for x in [1, 2, 3]:
    closures.append(make_closure(x))

for c in closures:
    print(c())

make_closure() could also be written like this, which may make it more readable:

def make_closure(var):
    return lambda: var
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
6

You can't create a new variable in the local scope inside the loop. Whatever name you choose, your function will always be a closure over that name and use its most recent value.

The easiest way around this is to use a keyword argument:

closures = []
for x in [1, 2, 3]:
    closures.append(lambda var=x: var)
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
1

In your example, var is always bound to the the last value of the loop. You need to bind it inside the lambda using closures.append(lambda var=var: var).

Jon Clements
  • 138,671
  • 33
  • 247
  • 280