Python: Bind an Unbound Method

Python: Bind an Unbound Method?

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

What is the difference between a function, an unbound method and a bound method?

A function is created by the def statement, or by lambda. Under Python 2, when a function appears within the body of a class statement (or is passed to a type class construction call), it is transformed into an unbound method. (Python 3 doesn't have unbound methods; see below.) When a function is accessed on a class instance, it is transformed into a bound method, that automatically supplies the instance to the method as the first self parameter.

def f1(self):
pass

Here f1 is a function.

class C(object):
f1 = f1

Now C.f1 is an unbound method.

>>> C.f1
<unbound method C.f1>
>>> C.f1.im_func is f1
True

We can also use the type class constructor:

>>> C2 = type('C2', (object,), {'f1': f1})
>>> C2.f1
<unbound method C2.f1>

We can convert f1 to an unbound method manually:

>>> import types
>>> types.MethodType(f1, None, C)
<unbound method C.f1>

Unbound methods are bound by access on a class instance:

>>> C().f1
<bound method C.f1 of <__main__.C object at 0x2abeecf87250>>

Access is translated into calling through the descriptor protocol:

>>> C.f1.__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>

Combining these:

>>> types.MethodType(f1, None, C).__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf87310>>

Or directly:

>>> types.MethodType(f1, C(), C)                
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>

The main difference between a function and an unbound method is that the latter knows which class it is bound to; calling or binding an unbound method requires an instance of its class type:

>>> f1(None)
>>> C.f1(None)
TypeError: unbound method f1() must be called with C instance as first argument (got NoneType instance instead)
>>> class D(object): pass
>>> f1.__get__(D(), D)
<bound method D.f1 of <__main__.D object at 0x7f6c98cfe290>>
>>> C.f1.__get__(D(), D)
<unbound method C.f1>

Since the difference between a function and an unbound method is pretty minimal, Python 3 gets rid of the distinction; under Python 3 accessing a function on a class instance just gives you the function itself:

>>> C.f1
<function f1 at 0x7fdd06c4cd40>
>>> C.f1 is f1
True

In both Python 2 and Python 3, then, these three are equivalent:

f1(C())
C.f1(C())
C().f1()

Binding a function to an instance has the effect of fixing its first parameter (conventionally called self) to the instance. Thus the bound method C().f1 is equivalent to either of:

(lamdba *args, **kwargs: f1(C(), *args, **kwargs))
functools.partial(f1, C())

Safely bind method from one class to another class in Python

Apparently, this is an intended behavior (which is cool!). But also apparently, this behavior is not very familiar.

If you knew Python 2 for a long time, you would might not be aware of the fact that Python 3 has no methods (as commented above).

So in Python 3:

>>> class Foo:
... def foo(self, name):
... print("Hello %s" % name)
...
>>> Foo.foo
<function Foo.foo at 0x7f729a406730>
>>> def foo():
... pass
...
>>> foo
<function foo at 0x7f729b83ff28>
>>>
>>> Foo.foo
<function Foo.foo at 0x7f729a406730>

There is no distinction! In Python 2 however:

Python 2.7.14 (default, Feb  2 2018, 02:17:12) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo:
... def foo(self, name):
... print("Hello %s" % name)
...
>>> Foo.foo
<unbound method Foo.foo>
>>>

Class method differences in Python: bound, unbound and static

In Python, there is a distinction between bound and unbound methods.

Basically, a call to a member function (like method_one), a bound function

a_test.method_one()

is translated to

Test.method_one(a_test)

i.e. a call to an unbound method. Because of that, a call to your version of method_two will fail with a TypeError

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

You can change the behavior of a method using a decorator

class Test(object):
def method_one(self):
print "Called method_one"

@staticmethod
def method_two():
print "Called method two"

The decorator tells the built-in default metaclass type (the class of a class, cf. this question) to not create bound methods for method_two.

Now, you can invoke static method both on an instance or on the class directly:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two

TypeError when calling an unbound method, but class _does_ define that method

loc is not an instance of IntegerBox, it is the IntegerBox class.

For example:

>>> class C(object):
... def m(self):
... pass
...
>>> C.m()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method m() must be called with C instance as first argument (got nothing instead)

but:

>>> c = C()    # create an instance
>>> c.m() # no error
>>>

You need to check what is being put into the stack object.

Edit: explain unbound methods

When a method is called on an instance, the instance is implicitly passed as the first parameter - this is the self parameter in the method signature. If the method is called on the Class rather than an instance, an instance must be explicitly passed otherwise a TypeError for an unbound method will be raised because the method is not "bound" to a specific instance of the class.

So:
C.m() raises a TypeError

C().m() is ok

C.m(C()) is also ok!



Related Topics



Leave a reply



Submit