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

python – 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 doesnt 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())

is quite hard to understand

Well, it is quite a hard topic, and it has to do with descriptors.

Lets start with function. Everything is clear here – you just call it, all supplied arguments are passed while executing it:

>>> f = A.__dict__[f1]
>>> f(1)
1

Regular TypeError is raised in case of any problem with number of parameters:

>>> f()
Traceback (most recent call last):
  File <stdin>, line 1, in <module>
TypeError: f1() takes exactly 1 argument (0 given)

Now, methods. Methods are functions with a bit of spices. Descriptors come in game here. As described in Data Model, A.f1 and A().f1 are translated into A.__dict__[f1].__get__(None, A) and type(a).__dict__[f1].__get__(a, type(a)) respectively. And results of these __get__s differ from the raw f1 function. These objects are wrappers around the original f1 and contain some additional logic.

In case of unbound method this logic includes a check whether first argument is an instance of A:

>>> f = A.f1
>>> f()
Traceback (most recent call last):
  File <stdin>, line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got nothing instead)
>>> f(1)
Traceback (most recent call last):
  File <stdin>, line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got int instance instead) 

If this check succeeds, it executes original f1 with that instance as first argument:

>>> f(A())
<__main__.A object at 0x800f238d0>

Note, that im_self attribute is None:

>>> f.im_self is None
True

In case of bound method this logic immediately supplies original f1 with an instance of A it was created of (this instance is actually stored in im_self attribute):

>>> f = A().f1
>>> f.im_self
<__main__.A object at 0x800f23950>
>>> f()
<__main__.A object at 0x800f23950>

So, bound mean that underlying function is bound to some instance. unbound mean that it is still bound, but only to a class.

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

A function object is a callable object created by a function definition. Both bound and unbound methods are callable objects created by a Descriptor called by the dot binary operator.

Bound and unbound method objects have 3 main properties: im_func is the function object defined in the class, im_class is the class, and im_self is the class instance. For unbound methods, im_self is None.

When a bound method is called, it calls im_func with im_self as the first parameter followed by its calling parameters. unbound methods call the underlying function with just its calling parameters.

Starting with Python 3, there are no unbound methods. Class.method returns a direct reference to the method.

Leave a Reply

Your email address will not be published.