Automatic binding using lambdas inside a loop

Question:

I am currently trying to create a loop that would bind event-action couples in a dict.
The callback function simply calls the action function and prints the parameters.

for binding, action in self.keyMap.revMap.items() :
        print binding, action
        self.top.bind(binding,
                      lambda event : self.callback(event, self.actionRep.__class__.__dict__[action]))

    print self.top.bind()

At the binding, I get these logs :

<Escape> toggleMenu -------------generated by the line "print binding, action"
<Return> toggleChat -------------generated by the line "print binding, action"
('<Key-Return>', '<Key-Escape>', '<Key>') ---generated by the line "print self.top.bind()"

The event-action couples are correct.
However, when the events occur, I have this :

<Tkinter.Event instance at 0x0000000002BB3988> <function toggleChat at 0x0000000002B60AC8>
<Tkinter.Event instance at 0x0000000002BB3948> <function toggleChat at 0x0000000002B60AC8>

that is, both the escape and the return event seem to be bound to toggleChat…

I have little experience of lambda expressions, but I would expect that a new nameless function is created for each of the loops. Am I wrong there ? If not, where could the problem be ?

Thanks in advance for insights.

Asked By: aPythonJourney

||

Answers:

The idiom for creating closures for lambdas is to pass the objects you wish to bind via default parameters

self.top.bind(binding, lambda event, action=action: self.callback(event, self.actionRep.__class__.__dict__[action]))
Answered By: John La Rooy

Lets first look at the behavior of regular functions:

x = 2
def foo():
    return x

x = 3
print foo() #3

Now we see that when a function picks up a variable from the enclosing namespace, it returns the current value of that variable, not the value when the function was defined. However, we can force it to take the value when it gets defined by creating a default argument as those are evaluated at function creation, not at the time it gets called.

x = 2
def foo(x=x):
    return x
x = 3
print foo() #2

lambda functions aren’t any different. New nameless functions are created in the loop, but when you actually go to evaluate the function, you pick up the value of the loop variable when the loop terminated. To get around this, we just just need to specify the value when we create the function:

funcs = [lambda i=i: i for i in range(10)]
print funcs[3]() #3
Answered By: mgilson
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.