use and abuse of 'lambda' as an event handler

The code below does not work - no matter which item in a menu is clicked, it will always print “4”, i.e. the last value in the list. Obviously I am missing something essential about the behavior of lambda and how it can properly be used to handle events. I use it for this purpose elsewhere without trouble, but I am always using static values, never iterating through an anonymous list like this. I would like to avoid clumsy workarounds if possible (I was having other, unrelated problems with my initial solution), but I’m also just curious why this fails.

thanks,

Nat

···

import wx

def menu_handler (i) :

print i

class MyFrame(wx.Frame):

def init (self, *args, **kwds) :

wx.Frame.init(self, *args, **kwds)

panel = wx.Panel(self, -1, size=(600,480))

btn = wx.Button(panel, -1, “Click me!”, pos=(240,200))

self.Bind(wx.EVT_BUTTON, self.OnClick, btn)

def OnClick (self, event) :

btn = event.GetEventObject()

menu = wx.Menu()

for i in range(5) :

item = menu.Append(-1, “print %d” % i)

self.Bind(wx.EVT_MENU, lambda evt: menu_handler(i), item)

btn.PopupMenu(menu)

menu.Destroy()

if name == “main” :

app = wx.App(0)

frame = MyFrame(None, -1, “Test”)

frame.Fit()

frame.Show()

app.MainLoop()

The problem is not the lambda, it's how python handles the closure.

See here for details: Passing Arguments to Callbacks - wxPyWiki

Personally I prefer the functools way.

-Matthias

···

Am 01.07.2010, 23:19 Uhr, schrieb Nat Echols <nathaniel.echols@gmail.com>:

The code below does not work - no matter which item in a menu is clicked, it
will always print "4", i.e. the last value in the list. Obviously I am
missing something essential about the behavior of lambda and how it can
properly be used to handle events. I use it for this purpose elsewhere
without trouble, but I am always using static values, never iterating
through an anonymous list like this. I would like to avoid clumsy
workarounds if possible (I was having other, unrelated problems with my
initial solution), but I'm also just curious why this fails.

Huh, weird. So this works:

self.Bind(wx.EVT_MENU, lambda evt, j=i: menu_handler(j), item)

but this does not:

self.Bind(wx.EVT_MENU, lambda evt: menu_handler(i), item)

Still not sure I understand why this is, but it solves my problem, anyway.

thanks,

Nat

···

On Thu, Jul 1, 2010 at 2:37 PM, Nitro nitrogenycs@googlemail.com wrote:

The problem is not the lambda, it’s how python handles the closure.

See here for details: http://wiki.wxpython.org/Passing%20Arguments%20to%20Callbacks

The difference has to do with when the value of i from the enclosing scope is evaluated.

···

On 7/1/10 2:43 PM, Nat Echols wrote:

On Thu, Jul 1, 2010 at 2:37 PM, Nitro <nitrogenycs@googlemail.com > <mailto:nitrogenycs@googlemail.com>> wrote:

    The problem is not the lambda, it's how python handles the closure.

    See here for details:
    Passing Arguments to Callbacks - wxPyWiki

Huh, weird. So this works:

       self.Bind(wx.EVT_MENU, lambda evt, j=i: menu_handler(j), item)

but this does not:

       self.Bind(wx.EVT_MENU, lambda evt: menu_handler(i), item)

Still not sure I understand why this is,

--
Robin Dunn
Software Craftsman