Search code examples
pythonclassmethodsbind

How to bind an unbound method without calling it?


In Python, is there a way to bind an unbound method without calling it?

I am writing a wxPython program, and for a certain class I decided it would be nice to group the data of all of my buttons together as a class-level list of tuples, like so:

class MyWidget(wx.Window):
    buttons = [
        ("OK", OnOK),
        ("Cancel", OnCancel)
    ]
 
    ...

    def setup(self):
        for text, handler in MyWidget.buttons:
            # This following line is the problem line.
            b = wx.Button(parent, label=text).bind(wx.EVT_BUTTON, handler)

The problem is, since all of the values of handler are unbound methods, my program explodes in a spectacular blaze and I weep.

I was looking around online for a solution to what seems like should be a relatively straightforward, solvable problem. Unfortunately I couldn’t find anything. Right now, I am using functools.partial to work around this, but does anyone know if there’s a clean-feeling, healthy, Pythonic way to bind an unbound method to an instance and continue passing it around without calling it?


Solution

  • All functions are also descriptors, so you can bind them by calling their __get__ method:

    bound_handler = handler.__get__(self, MyWidget)
    

    Here's R. Hettinger's excellent guide to descriptors.


    As a self-contained example pulled from Keith's comment:

    def bind(instance, func, as_name=None):
        """
        Bind the function *func* to *instance*, with either provided name *as_name*
        or the existing name of *func*. The provided *func* should accept the 
        instance as the first argument, i.e. "self".
        """
        if as_name is None:
            as_name = func.__name__
        bound_method = func.__get__(instance, instance.__class__)
        setattr(instance, as_name, bound_method)
        return bound_method
    
    class Thing:
        def __init__(self, val):
            self.val = val
    
    something = Thing(21)
    
    def double(self):
        return 2 * self.val
    
    bind(something, double)
    something.double()  # returns 42