1

I'm not sure I'm properly phrasing this question, code will probably be more clear.

I'm adding a pretty simple plugin system to a WxPython application, where the plugin options are simply added to a submenu, and once they are clicked, the plugin's action is performed. I'm pretty new to wx, so there could be something simple I'm doing wrong, so I'd be happy to be informed.

The following code works as expected , with the clicked menu option's plugin 'performAction' method being called.

for plugin in self.pluginManager.plugins:
    wxId = wx.NewId()
    plugin_submenu.Append(wxID,plugin.displayName)
    canvas.Bind(wx.EVT_MENU, plugin.performAction)

I'd like to add arguments to the plugin action as well, which led me to using lambdas, as the second argument to the 'bind' method should be callable. It seems like a pretty inconspicuous change to:

for plugin in self.pluginManager.plugins:
    wxId = wx.NewId()
    plugin_submenu.Append(wxID,plugin.displayName)
    canvas.Bind(wx.EVT_MENU, lambda evt, temp=objArgs: plugin.performAction(evt,temp)

as described by this link. However, when I do this, whichever plugin object gets bound last gets called, regardless of which menu option is actually clicked. I'm really not sure why the behavior changes from the first code sample to the second, but it does, and I'm stumped.

So, does anyone have any clue why this is occuring, and what can I do to get the behavior that I expect?

1 Answer 1

2

Your issue is python's scoping rules. In the first case, you've bound a specific instance of plugin to canvas, at the time this code is executed

In the second case, you've bound a specific function, your lambda function to the canvas, again at the time this code is executed. Unfortunately your lambda function refers to a plugin object which won't actually get evaluated until your event happens. When your event happens, this section of code will have long finished executing, and the actual plugin you'll be triggering will be the last one that was encountered.

One way to "trap" the actual plugin instance you want is to capture it in your lambda definition, by passing it in as an extra argument:

for plugin in self.pluginManager.plugins:
    wxId = wx.NewId()
    plugin_submenu.Append(wxID,plugin.displayName)
    canvas.Bind(wx.EVT_MENU, lambda evt, p=plugin, temp=objArgs:    
                p.performAction(evt,temp))

Not sure if I gave a good explanation of why/how this happens, but here is another explanation that might help.

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

3 Comments

So, in the first instance, I'm looping through plugin objects, and everything is working pretty much as expected. And in the second instance, the lambda doesn't get evaluated until the event fires?
Correct. Each iteration creates a new lambda function, but each lambda function will just end up referring to the same plugin. By assigning plugin to one of the lambda arguments, my example has "captured" the correct plugin as part of that lambda function instance.
Ugh. I probably would never have realized that was happening on my own, and I was under the assumption the issue was the wx's bind function. However, its not too bad now that I know about it. Thanks for the help.

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.