Class method differences in Python: bound, unbound and static

Viewed 159251

What is the difference between the following class methods?

Is it that one is static and the other is not?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()
13 Answers

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.

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'.

The call to method_two will throw an exception for not accepting the self parameter the Python runtime will automatically pass it.

If you want to create a static method in a Python class, decorate it with the staticmethod decorator.

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()

that is an error.

first of all, first line should be like this (be careful of capitals)

class Test(object):

Whenever you call a method of a class, it gets itself as the first argument (hence the name self) and method_two gives this error

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

The second one won't work because when you call it like that python internally tries to call it with the a_test instance as the first argument, but your method_two doesn't accept any arguments, so it wont work, you'll get a runtime error. If you want the equivalent of a static method you can use a class method. There's much less need for class methods in Python than static methods in languages like Java or C#. Most often the best solution is to use a method in the module, outside a class definition, those work more efficiently than class methods.

Bound method = instance method

Unbound method = static method.

The definition of method_two is invalid. When you call method_two, you'll get TypeError: method_two() takes 0 positional arguments but 1 was given from the interpreter.

An instance method is a bounded function when you call it like a_test.method_two(). It automatically accepts self, which points to an instance of Test, as its first parameter. Through the self parameter, an instance method can freely access attributes and modify them on the same object.

Unbound Methods

Unbound methods are methods that are not bound to any particular class instance yet.

Bound Methods

Bound methods are the ones which are bound to a specific instance of a class.

As its documented here, self can refer to different things depending on the function is bound, unbound or static.

Take a look at the following example:

class MyClass:    
    def some_method(self):
        return self  # For the sake of the example

>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()

>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>

# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given

# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2 
# arguments now instead of 1 

# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

Since we did not use the class instance — obj — on the last call, we can kinda say it looks like a static method.

If so, what is the difference between MyClass.some_method(10) call and a call to a static function decorated with a @staticmethod decorator?

By using the decorator, we explicitly make it clear that the method will be used without creating an instance for it first. Normally one would not expect the class member methods to be used without the instance and accesing them can cause possible errors depending on the structure of the method.

Also, by adding the @staticmethod decorator, we are making it possible to be reached through an object as well.

class MyClass:    
    def some_method(self):
        return self    

    @staticmethod
    def some_static_method(number):
        return number

>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

You can’t do the above example with the instance methods. You may survive the first one (as we did before) but the second one will be translated into an unbound call MyClass.some_method(obj, 10) which will raise a TypeError since the instance method takes one argument and you unintentionally tried to pass two.

Then, you might say, “if I can call static methods through both an instance and a class, MyClass.some_static_method and MyClass().some_static_method should be the same methods.” Yes!

Related