Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

The following code spits out 1 twice, I expect to see 0 and then 1

def pv(v) :
  print v


def test() :
  value = []
  value.append(0)
  value.append(1)
  x=[]
  for v in value :
    x.append(lambda : pv(v))
  return x

x = test()
for xx in x:
  xx()

I expected python lambdas to bind to the reference a local variable is pointing to, behind the scene. However that does not seem to be the case. I have enountered this problem in a large system where the lambda is doing modern C++'s equavalent of a bind ('boost::bind' for example) where in such case you would bind to a smart ptr or copy contstruct a copy for the lambda.

So, how do I bind a local variable to a lambda function and have it retain the correct reference when used? I'm quite gobsmacked with the behaviour since I would not expect this from a language with a garbage collector.

The code in question looks as follows (l3_e is the variable which is causing the problem) :

 for category in cat :
      for l2 in cat[category].entries :
        for l3 in cat[category].entries[l2].entry["sub_entries"] :
          l3_e = cat[category].entries[l2].entry["sub_entries"][l3]
          url = "http://forums.heroesofnewerth.com/" + l3_e.entry["url"]
          self.l4_processing_status[l3_e] = 0
          l3_discovery_requests.append( Request(
            url, callback = lambda response : self.parse_l4(response,l3_e)))
          print l3_e.entry["url"]
    return l3_discovery_requests
share|improve this question
1  
I've seen several variants of this question. someone needs to aggregate all of them, change the title to something memorable, and then tell the internet to google that – Shep May 4 '12 at 16:43
1  
ah, here you go, near duplicate: lexical-closures-in-python – Shep May 4 '12 at 16:48
Also, the first code chunk illustrates your point perfectly, why paste in this second chunk? – Shep May 4 '12 at 16:57
@shep, yes , I couldn't phrase it correctly, I was trying to replicate the way lua works. – Hassan Syed May 8 '12 at 20:52

2 Answers

up vote 9 down vote accepted

Change x.append(lambda : pv(v)) to x.append(lambda v=v: pv(v)).

You expect "python lambdas to bind to the reference a local variable is pointing to, behind the scene", but that is not how Python works. Python looks up the variable name at the time the function is called, not when it is created. Using a default argument works because default arguments are evaluated when the function is created, not when it is called.

This is not something special about lambdas. Consider:

x = "before foo defined"
def foo():
    print x
x = "after foo was defined"
foo()

prints

after foo was defined
share|improve this answer
interesting, could you elaborate on the intuition of the mechanics please. – Hassan Syed May 4 '12 at 16:38
1  
A more obvious alternative is using functools.partial or using a separate helper function to create the closures (def make_closure(v): return lambda: pv(v), you can put it into test). – delnan May 4 '12 at 16:40
so my real code should like lambda par1, par2 = l3_e : self.parse_l4(par1, par2) ? – Hassan Syed May 4 '12 at 16:46

The lambda's closure holds a reference to the variable being used, not its value, so if the value of the variable later changes, the value in the closure also changes. That is, the closure variable's value is resolved when the function is called, not when it is created. (Python's behavior here is not unusual in the functional programming world, for what it's worth.)

There are two solutions:

  1. Use a default argument, binding the current value of the variable to a local name at the time of definition. lambda v=v: pv(v)

  2. Use a double-lambda and immediately call the first one. (lambda v: lambda: pv(v))(v)

share|improve this answer
For #2) I hope you mean (lambda v: lambda: pv(v))(v). What you have currently doesn't work – newacct May 4 '12 at 23:16
Thanks, fixed. (The argument actually goes on the second lambda, as it gets returned by the first.) – kindall May 4 '12 at 23:33
I don't think that's right. It now evaluates to a 1-argument function instead of a 0-argument function – newacct May 4 '12 at 23:54
Yeah, you're right. – kindall May 5 '12 at 0:15

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.