Tim
Tim

Reputation: 99408

Differences between methods of a class, which are "function" and which are "bound method"?

I have experimented a little. By checking __dict__ of a class or an instance, I can see some method has type function and some bound method. The experiment is messy, and I can't figure the following questions out.

In Python 3, what are the differences between methods of a class or instance, which are "function" and which are "bound method"?

How are they created respectively?

Can they both be called on a class and on an instance? Will they both be implicitly given an instance as their first argument?

Is "bound method" an attribute of a class or an instance of a class?

Thanks.

Upvotes: 2

Views: 211

Answers (1)

MSeifert
MSeifert

Reputation: 152607

This answer will be really technical, I hope it's still understandable though. The problem is that it requires knowledge of the descriptor protocol to understand how methods in Python work.

All functions in Python 3 are descriptors, to be precise they are non-data descriptors. That means they implements a __get__ method - but no __set__ method.

That's interesting because descriptors can do (almost) anything if they are looked up on a class or an instance.

By the definition of the __get__ method in Pythons data model:

object.__get__(self, instance, owner)

Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). owner is always the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner. This method should return the (computed) attribute value or raise an AttributeError exception.

So what does this have to do with the difference between function and bound_method?

It's easy, a function accessed through __get__ with an instance=None will return itself:

>>> def func(x): return x
>>> func.__get__(None, object)
<function __main__.func>
>>> func.__get__(None, object) is func
True

While it will be a bound_method if accessed with an not-None instance:

>>> func.__get__(object())
<bound method func of <object object at 0x00000155614A0610>>

It's basically just a wrapper around func with the instance stored:

>>> m = func.__get__(object())
>>> m.__self__   # stored instance
<object at 0x155614a0650>

>>> m.__func__  # stored function
<function __main__.func>

However, when called, it will pass the instance as first argument to the wrapped function:

>>> m()
<object at 0x155614a0650>

So, bound methods will pass the instance as first argument, while functions do not (they requires all attributes).


So when you look at a class all normal methods will display as functions while all normal methods on an instance will be bound methods.

Why did I mention normal methods? Because you can define arbitrary descriptors. For example the Python built-ins already contain several exceptions:

  • classmethod
  • staticmethod
  • property (this is in fact a data-descriptor so I'll neglect it in the following discussion)

classmethods will display as bound method even when looked up on the class. That's because they are meant to be callable on the class and pass the class to the function, no matter if they are called on the class or the instance:

class Test(object):
    @classmethod
    def func(x):
        return x
    
>>> Test.func
<bound method Test.func of <class '__main__.Test'>>
>>> Test().func
<bound method Test.func of <class '__main__.Test'>>

And staticmethods always display as functions because they never pass anything additional to the function:

class Test(object):
    @staticmethod
    def func(x):
        return x
    
>>> Test().func
<function __main__.Test.func>
>>> Test.func
<function __main__.Test.func>

So it's easily possible to see also bound methods on the class (e.g. classmethods) and likewise one could also find normal functions on instances (e.g. staticmethods).

Upvotes: 6

Related Questions