In this blog post we are going to explain what types of methods exist, how they work, and how they are different from each other (and from functions!).

At the end of this post, you should have a solid understanding of the Python Methods and Object-Oriented Programming in general.

To get an idea of what methods are, think of them as normal functions that live within a class with a minor twist.

When we call a method Python will do some work behind the scenes, which is why they behave differently from normal functions.

There are 3 types of methods: instance methods, class methods, and static methods.

What is an Instance Method?

Instance methods are the most common, and the default type of method.

They look like normal functions, live inside a class, and require one special parameter, usually called self.

When we call a method, we don't pass in a value for this first parameter ourselves. Python takes care of that and passes the caller object as the first argument to the method call. That allows us to access different properties of the object instance within the method.

What is the caller object? Let's go through an example:

class Student:
    def __init__(self, name, grades):
        self.name = name
        self.grades = grades

    def average(self):
        return sum(self.grades) / len(self.grades)

college_student = Student('Rolf', [24, 55, 98, 100])

print(college_student.average())  # 69.25

Here we have created a Student class with two instance methods: __init__ and average.

We are calling the method on the object instance like a normal function, with parentheses: average() but with a period between the object and the method name. This signals that the method is inside the object.

Let's take a look at each method separately:

def __init__(self, name, grades):
    self.name = name
    self.grades = grades

The __init__ method gets called when we create a new object. It has the self parameter as well as any other number of parameters. When creating an object, we pass in values for every parameter except for self, like so:

college_student = Student('Rolf', [24, 55, 98, 100])

The other method in our class is called average:

def average(self):
    return sum(self.grades) / len(self.grades)

This method has just the self parameter. Python will automatically give this parameter a value when we call the method on an object we've created.

Let's create an object and call the method on it:

college_student = Student('Rolf', [24, 55, 98, 100])

print(college_student.average())  # 69.25

Here's how the flow of function calls goes in this code:

  1. First we create an object with Student('Rolf', [24, 55, 98, 100]). This calls the __init__ method of our class and passes the two arguments as the values for name and grades.
    • The value of self is provided by Python. It's an empty object.
    • We then set two properties inside the object, self.name and self.grades. We could also set any other properties we want!
  2. This returns the object we've created and assigns it to the college_student variable.
  3. When we call college_student.average(), Python automatically passes college_student as the value for self, so inside the method we have access to the properties we set earlier, inside __init__.

Let us create one more object to see what happens.

working_student = Student('Anna', [88, 79, 90, 99, 100])

print(working_student.average())  # 91.2

We can now see that the instance method accesses properties from each individual object instance.

Here is an example code that illustrates what Python does in the background to help you understand this theory better:

print(Student.average(working_student))  # Caller object

When should you use instance methods?

  • Instance methods are your go-to when it comes to Python Object Oriented Programming.
  • Use instance methods when the method is going to access properties of the caller object.

What is a Class Method?

As their name implies, class methods can be accessed through the class. They receive the caller’s class as an argument and do not require an object to be created at all.

Class methods still require a special parameter, but instead of self it is usually called cls (short for class). To define a class method, we use the @classmethod decorator.

Here is a short example:

class Bank:
    @classmethod
    def make_loan(cls, amount):
        print(f"You applied for a loan of ${amount}.")

Bank.make_loan(1500)  # You applied for a loan of $1500.

When to use class methods?

  • Use class methods as you need to access the state of the class and not only an object instance.
  • They can prove useful when you want to make changes across all the instances of the class.
  • An added benefit to class methods is that they automatically use the sub-class if you are in an inheritance scenario.

Here we can see the example of an inheritance scenario where we use the class method from_sum:

class FixedFloat:
    def __init__(self, amount):
        self.amount = amount

    def __repr__(self):
        return f'<FixedFloat {self.amount:.2f}>'

    @classmethod
    def from_sum(cls, value1, value2):
        return cls(value1 + value2)

class Euro(FixedFloat):
    def __init__(self, amount):
        super().__init__(amount)
        self.symbol = '€'

    def __repr__(self):
        return f'<Euro {self.symbol}{self.amount:.2f}>'

print(FixedFloat.from_sum(16.7565, 90))  # <FixedFloat 106.76>
print(Euro.from_sum(16.7565, 90))  # <Euro €106.75>

We’ve defined the Euro class that extends the FixedFloat class. It’s got an __init__ method that calls the parent’s __init__, and a __repr__ method that overrides the parents’. It doesn’t have a from_sum method as that’s inherited and we’ll just use the one the parent defined.

Now you may be wondering: why not just create the object as we do normally, with FixedFloat(value1 + value2) instead of cls(value1 + value2)?

Let's see what would happen if we hard code the method with FixedFloat. Pay close attention to the class method:

class FixedFloat:
    def __init__(self, amount):
        self.amount = amount

    def __repr__(self):
        return f'<FixedFloat {self.amount:.2f}>'

    @classmethod
    def from_sum(cls, value1, value2):
        return FixedFloat(value1 + value2)


class Euro(FixedFloat):
    def __init__(self, amount):
        super().__init__(amount)
        self.symbol = '€'

    def __repr__(self):
        return f'<Euro {self.symbol}{self.amount:.2f}>'

We have changed the code slightly so that the from_sum method is now calling FixedFloat(value1 + value2) instead of cls(value1 + value2).

However, look at what happens when we try to use that method from the Euro class:

print(FixedFloat.from_sum(16.7565, 90))  # <FixedFloat 106.76>
print(Euro.from_sum(16.7565, 90))  # <FixedFloat €106.75

Since Euro.from_sum is using the superclass implementation, it returns a FixedFloat! This is most likely not what we want, and it is the main reason for using the cls parameter with @classmethod instead.

Python will automatically give us the caller class instead of the one we hard code inside the method itself. This means the code is more flexible, and we don't have to re-implement the same thing in the subclasses just to return the correct type of object.

What is a Static Method?

Static methods are regular functions that live inside the class in order to indicate a certain level of relation to it.

They do not require a special parameter and do not receive the object instance or the class instance as arguments by default. What they do require instead is the @staticmethod decorator.

We call them self-contained methods because they do not have access to the instance or class like the other types of method do.

When should you use static methods?

Static methods should be used when we do not need to access the class or the instance. For example, when printing data or performing an arithmetic operation.

Here we generate a random string of k characters for the user using a static method:

import random

class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    @staticmethod
    def generate_password():
        return ''.join(random.choices('abc1234567890', k=8))

print(User.generate_password())

We can see that by placing generate_password as a static method inside the User class using the @staticmethod decorator, the method no longer expects a special parameter, or indeed any required parameters at all.

An advantage between using a static method versus a normal function is that by grouping them inside a class it could make more sense because static methods indicate that they may only be used in relation to the class.

Summary

  • The instance method receives the caller object as the first parameter, and it requires no decorator.
  • The class method receives the caller class as the first parameter, and it requires the @classmethod decorator.
  • The static method does not take any necessary parameter, and it requires the @staticmethod decorator.

Conclusion

There we go! We've gone through every type of Python methods and now you can differentiate and use them! We hope you enjoyed this blog post. Don't forget to subscribe to our newsletter to get notified when we publish new blog posts!

Fun fact: Did you know that the difference between static methods and class methods is one of the most asked questions in job interviews?

Let us know what you think! Join our Discord community today!
https://discord.gg/rC2wKa6dXb