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
Python 3 Importerror: No Module Named 'Configparser'
Splitting a List into N Parts of Approximately Equal Length
String Concatenation of Two Pandas Columns
Correct Way to Define Python Source Code Encoding
How to Set Sys.Stdout Encoding in Python 3
Importerror: No Module Named 'Tkinter'
Pandas Groupby.Apply Method Duplicates First Group
Checking Multiple Values for a Variable
Comprehensive Beginner's Virtualenv Tutorial
Difference Between _Getattr_ and _Getattribute_
How to Add an Extra Column to a Numpy Array
How to Add Property to a Class Dynamically
What Is the Purpose of the -M Switch
How to Check If a String Contains an Element from a List in Python
Mkdir -P Functionality in Python
Python: Execute Cat Subprocess in Parallel
What Are "First-Class" Objects
Rank Items in an Array Using Python/Numpy, Without Sorting Array Twice