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?
-
7@Christopher - A method that isn't bound to the scope of the object it was sucked from, so you have to pass self explicitly.Aiden Bell– Aiden Bell2009年06月18日 21:42:47 +00:00Commented Jun 18, 2009 at 21:42
6 Answers 6
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
9 Comments
MethodType one, because it works the same in py3k, while MethodType's arguments have been changed up a bit.bind = lambda instance, func, asname: setattr(instance, asname, func.__get__(instance, instance.__class__)) Example: class A: pass; a = A(); bind(a, bind, 'bind')__get__ will take that implicitly from the object parameter. I'm not even sure if supplying it does anything, as it makes no difference what type I supply as the second parameter regardless of what the first parameter is an instance of. So bind = lambda instance, func, asname=None: setattr(instance, asname or func.__name__, func.__get__(instance)) should do the trick as well. (Though I'd prefer having bind usable as a decorator, personally, but that's a different matter.)__dict__ and attribute access gives you unbound or bound methods through the normal descriptor protocol. I always assumed it was some sort of magic that happened during type.__new__()This can be done with types.MethodType:
import types
bound_handler = types.MethodType(handler, self)
4 Comments
__get__). I don't know for which version of python this you tested this on, but on python 3.4, the MethodType function takes two arguments. The function and the instance. So this should be changed to types.MethodType(f, C()).wgt.flush = types.MethodType(lambda self: None, wgt)With a closure, also known as a closed expression (as opposed to an open expression), which is an expression without free variables:
bound_handler = (lambda handler, self:
lambda *args, **kwargs: handler(self, *args, **kwargs)
)(handler, self)
Here handler and self are free variables in the inner lambda expression and bound variables in the outer lambda expression, and args and kwargs are bound variables in both the inner and outer lambda expressions, so the outer lambda expression is a closure.
1 Comment
functools.partial(handler, self)This will bind self to handler:
bound_handler = lambda *args, **kwargs: handler(self, *args, **kwargs)
This works by passing self as the first argument to the function. obj.f() is just syntactic sugar for f(obj).
5 Comments
functools.partial instead of defining a lambda. It doesn't solve the exact problem, though. You're still dealing with a function instead of an instancemethod.function whose first argument you partial-ed and instancemethod; duck typing can't see the difference.functools.partial drops some metadata, e.g. __module__. (Also I wanna state for the record I cringe real hard when I look at my first comment on this answer.) In fact in my question I mention I'm already using functools.partial but I felt like there had to be a "purer" way, since it's easy to get both unbound and bound methods.Late to the party, but I came here with a similar question: I have a class method and an instance, and want to apply the instance to the method.
At the risk of oversimplifying the OP's question, I ended up doing something less mysterious that may be useful to others who arrive here (caveat: I'm working in Python 3 -- YMMV).
Consider this simple class:
class Foo(object):
def __init__(self, value):
self._value = value
def value(self):
return self._value
def set_value(self, value):
self._value = value
Here's what you can do with it:
>>> meth = Foo.set_value # the method
>>> a = Foo(12) # a is an instance with value 12
>>> meth(a, 33) # apply instance and method
>>> a.value() # voila - the method was called
33
2 Comments
meth to be invokable without having to send it the a argument (which is why I initially used functools.partial) - but this is preferable if you don't need to pass the method around and can just invoke it on the spot. Also this works the same way in Python 2 as it does in Python 3.To expand on @Keith Pinson’s answer which uses a closure and @brian-brazil’s answer which does not, here is why the former is correct.
Example with a closure:
def handler(self, *args, **kwargs):
return self
self = True
bound_handler = (lambda handler, self:
lambda *args, **kwargs: handler(self, *args, **kwargs)
)(handler, self)
bound_handler() # returns True
self = False
bound_handler() # returns True
handler = print
bound_handler() # returns True
The outer lambda expression is closed so its bound variables handler and self cannot be rebound. __get__, types.MethodType, and functools.partial have the same behaviour.
Example without a closure:
def handler(self, *args, **kwargs):
return self
self = True
bound_handler = lambda *args, **kwargs: handler(self, *args, **kwargs)
bound_handler() # returns True
self = False
bound_handler() # returns False
handler = print
bound_handler() # prints False
The lambda expression is open so its free variables handler and self can be rebound.