-1

I have a pretty complex Tkinter GUI so it's worth while to avoid repeating work assigning controls by using lambda functions. Here the code extract:

    def createRightMenu(self, treeName, commands: []):
       for cmd in commands:
          self.rightMouse[treeName].add_command(label= cmd['label'], command= lambda: self.execCommand(cmd['reqId']))
       self.tree[treeName].bind("<Button-3>", lambda event: 
       self.rightMouse_click(event.x_root, event.y_root, treeName))

    def execCommand(self, reqId):
       print("execCommand", reqId)

    self.tree = {}
    treeName = 'a'
    self.createTree(root, treeName)
    self.createRightMenu(treeName , [
        {'label': 'copy', 'reqId': 1},
        {'label': 'retrieve', 'reqId': 2},
        ])

What does work? In my right click menu I get the entries 'copy' and 'retrieve' and execCommand is called.

What does NOT work? execCommand gets ALWAYS the last list element, i.e. 'retrieve', 2 as parameter even if I right click on 'copy'. If I manually add the two .add_command's all is fine. Only using a for loop doesn't work. Any idea why?

VengaVenga
  • 680
  • 1
  • 10
  • 13

1 Answers1

1

Scoping issue: when the lambda fires it checks the scope for the cmd variable. During each iteration of for cmd in commands:, the cmd variable is set to a new value. At the end of the iteration, the cmd variable in the still equals the final iteration of for cmd in commands: (i.e.- {'label': 'retrieve', 'reqId': 2}).

The solution is to specify a "new" variable in the signature of the lambda, which it will then use because of how scoping works in python (quotes on "new" because it can be a variable with the same name). Specifically, you could change your code to something like:

for cmd in commands:
    self.rightMouse[treeName].add_command(label= cmd['label'],
                                          command= lambda reqid = cmd['reqId']: self.execCommand(reqid))

Below is a script to demonstrate what I'm talking about here by example:

lambdas = []
words = ["Hello","World"]
print("Creating Lambds")
for word in words:
    my_lambda = lambda: word
    print('>>> Result for "{}" lambda: "{}"'.format(word,my_lambda()))
    lambdas.append(my_lambda)

print("Post Loop Lambda Results")
for w,_lambda in zip(words,lambdas):
    print('>>> Result for "{}" lambda: "{}"'.format(w,_lambda()))

word = "Foobar"
print('Setting word variable to "{}"'.format(word))
print("New Results for lambdas:")
for w,_lambda in zip(words,lambdas):
    print('>>> Result for "{}" lambda: "{}"'.format(w,_lambda()))

print("----------------\nSolution:\n")
lambdas = []
words = ["Hello","World"]
print("Creating Lambds")
for word in words:
    my_lambda = lambda myword = word: myword
    print('>>> Result for "{}" lambda: "{}"'.format(word,my_lambda()))
    lambdas.append(my_lambda)

print("Post Loop Lambda Results")
for w,_lambda in zip(words,lambdas):
    print('>>> Result for "{}" lambda: "{}"'.format(w,_lambda()))

word = "Foobar"
print('Setting word variable to "{}"'.format(word))
print("New Results for lambdas:")
for w,_lambda in zip(words,lambdas):
    print('>>> Result for "{}" lambda: "{}"'.format(w,_lambda()))

Heres some links:

Reid Ballard
  • 1,480
  • 14
  • 19