Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class method differences in Python: bound, unbound and static

People also ask

What is the difference between class method and static method in Python?

Class method can access and modify the class state. Static Method cannot access or modify the class state. The class method takes the class as parameter to know about the state of that class. Static methods do not know about class state.

What the difference between bound and unbound methods?

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.

What is bound and unbound in Python?

An unbound method is essentially a function with some trimmings. A 'bound method' is called that because the first argument (ie self ) is already set to a ; you can call b(10) and it works just the same way as if you had done a. fred(10) (this is actually necessary given how CPython operates).

Is unbounded a class method in Python?

Methods in Python are like functions except that it is attached to an object. The methods are called on objects and it possibly make changes to that object. These methods can be Bound, Unbound or Static method. The static methods are one of the types of Unbound method.


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

Methods in Python are a very, very simple thing once you understood the basics of the descriptor system. Imagine the following class:

class C(object):
    def foo(self):
        pass

Now let's have a look at that class in the shell:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

As you can see if you access the foo attribute on the class you get back an unbound method, however inside the class storage (the dict) there is a function. Why's that? The reason for this is that the class of your class implements a __getattribute__ that resolves descriptors. Sounds complex, but is not. C.foo is roughly equivalent to this code in that special case:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

That's because functions have a __get__ method which makes them descriptors. If you have an instance of a class it's nearly the same, just that None is the class instance:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Now why does Python do that? Because the method object binds the first parameter of a function to the instance of the class. That's where self comes from. Now sometimes you don't want your class to make a function a method, that's where staticmethod comes into play:

 class C(object):
  @staticmethod
  def foo():
   pass

The staticmethod decorator wraps your class and implements a dummy __get__ that returns the wrapped function as function and not as a method:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Hope that explains it.


When you call a class member, Python automatically uses a reference to the object as the first parameter. The variable self actually means nothing, it's just a coding convention. You could call it gargaloo if you wanted. That said, the call to method_two would raise a TypeError, because Python is automatically trying to pass a parameter (the reference to its parent object) to a method that was defined as having no parameters.

To actually make it work, you could append this to your class definition:

method_two = staticmethod(method_two)

or you could use the @staticmethod function decorator.


>>> class Class(object):
...     def __init__(self):
...         self.i = 0
...     def instance_method(self):
...         self.i += 1
...         print self.i
...     c = 0
...     @classmethod
...     def class_method(cls):
...         cls.c += 1
...         print cls.c
...     @staticmethod
...     def static_method(s):
...         s += 1
...         print s
... 
>>> a = Class()
>>> a.class_method()
1
>>> Class.class_method()    # The class shares this value across instances
2
>>> a.instance_method()
1
>>> Class.instance_method() # The class cannot use an instance method
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
>>> Class.instance_method(a)
2
>>> b = 0
>>> a.static_method(b)
1
>>> a.static_method(a.c) # Static method does not have direct access to 
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5

method_two won't work because you're defining a member function but not telling it what the function is a member of. If you execute the last line you'll get:

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

If you're defining member functions for a class the first argument must always be 'self'.


Accurate explanation from Armin Ronacher above, expanding on his answers so that beginners like me understand it well:

Difference in the methods defined in a class, whether static or instance method(there is yet another type - class method - not discussed here so skipping it), lay in the fact whether they are somehow bound to the class instance or not. For example, say whether the method receives a reference to the class instance during runtime

class C:
    a = [] 
    def foo(self):
        pass

C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C() 
c # this is the class instance

The __dict__ dictionary property of the class object holds the reference to all the properties and methods of a class object and thus

>>> C.__dict__['foo']
<function foo at 0x17d05b0>

the method foo is accessible as above. An important point to note here is that everything in python is an object and so references in the dictionary above are themselves pointing to other objects. Let me call them Class Property Objects - or as CPO within the scope of my answer for brevity.

If a CPO is a descriptor, then python interpretor calls the __get__() method of the CPO to access the value it contains.

In order to determine if a CPO is a descriptor, python interpretor checks if it implements the descriptor protocol. To implement descriptor protocol is to implement 3 methods

def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

for e.g.

>>> C.__dict__['foo'].__get__(c, C)

where

  • self is the CPO (it could be an instance of list, str, function etc) and is supplied by the runtime
  • instance is the instance of the class where this CPO is defined (the object 'c' above) and needs to be explicity supplied by us
  • owner is the class where this CPO is defined(the class object 'C' above) and needs to be supplied by us. However this is because we are calling it on the CPO. when we call it on the instance, we dont need to supply this since the runtime can supply the instance or its class(polymorphism)
  • value is the intended value for the CPO and needs to be supplied by us

Not all CPO are descriptors. For example

>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510> 
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'

This is because the list class doesnt implement the descriptor protocol.

Thus the argument self in c.foo(self) is required because its method signature is actually this C.__dict__['foo'].__get__(c, C) (as explained above, C is not needed as it can be found out or polymorphed) And this is also why you get a TypeError if you dont pass that required instance argument.

If you notice the method is still referenced via the class Object C and the binding with the class instance is achieved via passing a context in the form of the instance object into this function.

This is pretty awesome since if you chose to keep no context or no binding to the instance, all that was needed was to write a class to wrap the descriptor CPO and override its __get__() method to require no context. This new class is what we call a decorator and is applied via the keyword @staticmethod

class C(object):
  @staticmethod
  def foo():
   pass

The absence of context in the new wrapped CPO foo doesnt throw an error and can be verified as follows:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Use case of a static method is more of a namespacing and code maintainability one(taking it out of a class and making it available throughout the module etc).

It maybe better to write static methods rather than instance methods whenever possible, unless ofcourse you need to contexualise the methods(like access instance variables, class variables etc). One reason is to ease garbage collection by not keeping unwanted reference to objects.