Python Language Classes Bound, unbound, and static methods

Help us to keep this website almost Ad Free! It takes only 10 seconds of your time:
> Step 1: Go view our video on YouTube: EF Core Bulk Extensions
> Step 2: And Like the video. BONUS: You can also share it!

Example

The idea of bound and unbound methods was removed in Python 3. In Python 3 when you declare a method within a class, you are using a def keyword, thus creating a function object. This is a regular function, and the surrounding class works as its namespace. In the following example we declare method f within class A, and it becomes a function A.f:

Python 3.x3.0
class A(object):
    def f(self, x):
        return 2 * x
A.f
# <function A.f at ...>  (in Python 3.x)

In Python 2 the behavior was different: function objects within the class were implicitly replaced with objects of type instancemethod, which were called unbound methods because they were not bound to any particular class instance. It was possible to access the underlying function using .__func__ property.

Python 2.x2.3
A.f
# <unbound method A.f>   (in Python 2.x)
A.f.__class__
# <type 'instancemethod'>
A.f.__func__
# <function f at ...>

The latter behaviors are confirmed by inspection - methods are recognized as functions in Python 3, while the distinction is upheld in Python 2.

Python 3.x3.0
import inspect

inspect.isfunction(A.f)
# True
inspect.ismethod(A.f)
# False
Python 2.x2.3
import inspect

inspect.isfunction(A.f)
# False
inspect.ismethod(A.f)
# True

In both versions of Python function/method A.f can be called directly, provided that you pass an instance of class A as the first argument.

A.f(1, 7)
# Python 2: TypeError: unbound method f() must be called with
#                      A instance as first argument (got int instance instead) 
# Python 3: 14   
a = A()
A.f(a, 20)
# Python 2 & 3: 40

Now suppose a is an instance of class A, what is a.f then? Well, intuitively this should be the same method f of class A, only it should somehow "know" that it was applied to the object a – in Python this is called method bound to a.

The nitty-gritty details are as follows: writing a.f invokes the magic __getattribute__ method of a, which first checks whether a has an attribute named f (it doesn't), then checks the class A whether it contains a method with such a name (it does), and creates a new object m of type method which has the reference to the original A.f in m.__func__, and a reference to the object a in m.__self__. When this object is called as a function, it simply does the following: m(...) => m.__func__(m.__self__, ...). Thus this object is called a bound method because when invoked it knows to supply the object it was bound to as the first argument. (These things work same way in Python 2 and 3).

a = A()
a.f
# <bound method A.f of <__main__.A object at ...>>
a.f(2)
# 4

# Note: the bound method object a.f is recreated *every time* you call it:
a.f is a.f  # False
# As a performance optimization you can store the bound method in the object's
# __dict__, in which case the method object will remain fixed:
a.f = a.f
a.f is a.f  # True

Finally, Python has class methods and static methods – special kinds of methods. Class methods work the same way as regular methods, except that when invoked on an object they bind to the class of the object instead of to the object. Thus m.__self__ = type(a). When you call such bound method, it passes the class of a as the first argument. Static methods are even simpler: they don't bind anything at all, and simply return the underlying function without any transformations.

class D(object):
    multiplier = 2

    @classmethod
    def f(cls, x):
        return cls.multiplier * x

    @staticmethod
    def g(name):
        print("Hello, %s" % name)

D.f
# <bound method type.f of <class '__main__.D'>>
D.f(12)
# 24
D.g
# <function D.g at ...>
D.g("world")
# Hello, world

Note that class methods are bound to the class even when accessed on the instance:

d = D()
d.multiplier = 1337
(D.multiplier, d.multiplier)
# (2, 1337)
d.f
# <bound method D.f of <class '__main__.D'>>
d.f(10)
# 20

It is worth noting that at the lowest level, functions, methods, staticmethods, etc. are actually descriptors that invoke __get__, __set__ and optionally __del__ special methods. For more details on classmethods and staticmethods:



Got any Python Language Question?