Passing Arguments to Callbacks

When binding events to callback, wxPython expects the call back to be a function that takes a single argument: the event object. There are often times when one might want to pass additional information into a callback function. A common use is when you have a bunch of widgets that all behave similarly, so you don't want to write a different callback function for each one. One way to handle that is to have them all bound to the same callback, and then in the callback, figure out which widget the event came from by using Event.GetEventObject() or Event.GetId().

I find it more elegant to pass data directly into the callback, using the "lambda trick". lambda allows you to create a simple function in place in your code. So you can create a function that wraps the callback, passing some extra info in with keyword arguments. See the example below for a working application.

btn.Bind(wx.EVT_BUTTON, lambda evt, temp=button_name: self.OnButton(evt, temp) )

Bind() takes two (or more) arguments:

the event type and the callback.

The callback should be a python callable that takes one argument -- the event object. In the usual case, that's easy, something like:

self.OnButton

where:

   1 def OnButton(self, event):
   2    some_stuff

the self is the class instance object, so the event gets passed through as the only argument.

In this case, we are trying to pass some extra info into the callback: button_name. so we use a lamba:

lambda evt, temp=button_name:

creates a function that takes two arguments, but only the first (the event) is required, and the other has a default value. The way python keyword bindings work is that "temp" is bound to the OBJECT that is bound to button_name When the function is created -- that's key, 'cause this is in a loop, and that name is bound to a different object each time through the loop, and a new function object is created each time, each with a different object bound to "temp".

what the lambda function returns is return value of self.OnButton(evt, temp), with temp set to the button_name object.

so, the callback mechanism gets a callable that takes an event as an argument, and the OnButton method gets called with that event, and one other argument.

This is a simple case that could be handled in other ways, but remember that the extra data that is passed in can be any python object, indeed, more than one if you want.

Note that there is nothing special about lambda here -- it just lets you cram it all onto one line. You could do:

   1 def callback(evt, temp=button_name):
   2     return self.Onbutton(evt, temp)
   3 
   4 btn.Bind(wx.EVT_BUTTON, callback )

and it would mean exactly the same thing.

Example

   1 #!/usr/bin/env python
   2 
   3 import wx
   4 
   5 class DemoFrame(wx.Frame):
   6     """ This window displays a set of buttons """
   7     def __init__(self, *args, **kwargs):
   8         wx.Frame.__init__(self, *args, **kwargs)
   9 
  10         sizer = wx.BoxSizer(wx.VERTICAL)
  11         for button_name in ["first", "second", "third"]:
  12             btn = wx.Button(self, label=button_name)
  13             btn.Bind(wx.EVT_BUTTON, lambda evt, temp=button_name: self.OnButton(evt, temp) )
  14             sizer.Add(btn, 0, wx.ALL, 10)
  15 
  16         self.SetSizerAndFit(sizer)
  17         
  18     def OnButton(self, Event, button_label):
  19         print "In OnButton:", button_label
  20 
  21 
  22 app = wx.App(False)
  23 frame = DemoFrame(None, title="Lambda Bind Test")
  24 frame.Show()
  25 app.MainLoop()

Example 2 - Inline functions, no lambdas

   1 import wx
   2 
   3 class DemoFrame(wx.Frame):
   4     """ This window displays a set of buttons """
   5     def __init__(self, *args, **kwargs):
   6         wx.Frame.__init__(self, *args, **kwargs)
   7 
   8         sizer = wx.BoxSizer(wx.VERTICAL)
   9         for button_name in ["first", "second", "third"]:
  10             btn = wx.Button(self, label=button_name)
  11 
  12             def OnButton(event, button_label=button_name):
  13                 print "In OnButton:", button_label
  14 
  15             btn.Bind(wx.EVT_BUTTON, OnButton)
  16             sizer.Add(btn, 0, wx.ALL, 10)
  17         self.SetSizerAndFit(sizer)
  18 
  19 
  20 app = wx.App(False)
  21 frame = DemoFrame(None, title="Lambda Bind Test")
  22 frame.Show()
  23 app.MainLoop()

Alternative way using functools

If you are on Python 2.5 or higher, you can also make use of the functools package instead of using lambda for this. Here's the example from above written using functools.partial:

   1 #!/usr/bin/env python
   2 
   3 from functools import partial
   4 import wx
   5 
   6 class DemoFrame(wx.Frame):
   7     """ This window displays a set of buttons """
   8     def __init__(self, *args, **kwargs):
   9         wx.Frame.__init__(self, *args, **kwargs)
  10 
  11         sizer = wx.BoxSizer(wx.VERTICAL)
  12         for button_name in ["first", "second", "third"]:
  13             btn = wx.Button(self, label=button_name)
  14             btn.Bind(wx.EVT_BUTTON, partial( self.OnButton, button_label = button_name ) )
  15             sizer.Add(btn, 0, wx.ALL, 10)
  16 
  17         self.SetSizerAndFit(sizer)
  18         
  19     def OnButton(self, Event, button_label):
  20         print "In OnButton:", button_label
  21 
  22 
  23 app = wx.App(False)
  24 frame = DemoFrame(None, title="functools.partial Bind Test")
  25 frame.Show()
  26 app.MainLoop()

For more information on functools.partial see also http://docs.python.org/library/functools.html .

Passing Arguments to Callbacks (last edited 2011-07-29 18:48:24 by c-98-246-90-205)