Skip to main content

Instance, Class, and Static Methods

We've learned how to define methods—functions inside a class—that give our objects behavior. So far, all the methods we've created have been instance methods, which operate on a specific instance of a class.

However, Python provides two other types of methods: class methods and static methods. Each has a distinct purpose and is decorated differently. Understanding when to use each type is key to designing well-structured and intuitive classes.


📚 Prerequisites

You should be comfortable with the basics of classes, including instance attributes and the self parameter.


🎯 Article Outline: What You'll Master

In this article, you will learn:

  • Instance Methods: A review of the most common method type, which uses self.
  • Class Methods: How to use the @classmethod decorator to create methods that operate on the class itself, not the instance.
  • Static Methods: How to use the @staticmethod decorator to create utility functions that are logically related to a class but don't operate on the instance or class.
  • Clear Use Cases: Understand when to choose one type of method over the others.

🧠 Section 1: Instance Methods (The Default)

This is the type of method we've been using all along. An instance method is bound to a specific object, and its first argument is always self, which refers to that object.

Purpose: To access or modify the state (the instance attributes) of a specific object.

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

# This is an instance method
def display_info(self):
# It uses 'self' to access the instance's own data
print(f"Student: {self.name}, Grade: {self.grade}")

def promote(self):
# It can also modify the instance's data
self.grade += 1
print(f"{self.name} has been promoted to grade {self.grade}.")

# Create an instance
s1 = Student("Alice", 9)

# Call instance methods on that specific instance
s1.display_info()
s1.promote()

Most of the methods you write will be instance methods.


💻 Section 2: Class Methods - Working with the Blueprint

A class method is bound to the class and not the object. It doesn't know anything about a specific instance's attributes (like a student's name). Instead, its first argument is the class itself, which is conventionally named cls.

To create a class method, you must use the @classmethod decorator.

Purpose: To work with class-level data or to create "factory methods" that provide alternative ways to create instances of the class.

Example: A Factory Method Imagine we want to create Student objects from a dictionary. A class method is a perfect factory for this.

class Student:
school_name = "Python High School" # A class attribute

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

def display_info(self):
print(f"Student: {self.name}, Grade: {self.grade}, School: {self.school_name}")

# This is a class method
@classmethod
def from_dict(cls, student_data: dict):
"""A factory method to create a Student from a dictionary."""
# 'cls' here refers to the Student class itself.
# Calling cls(name, grade) is the same as calling Student(name, grade).
return cls(student_data['name'], student_data['grade'])

@classmethod
def get_school_name(cls):
"""A method to access a class attribute."""
return f"All students attend {cls.school_name}."


# --- Let's use our class methods ---

# You can call a class method directly on the class
print(Student.get_school_name())

# Use the factory method to create a new instance
student_info = {'name': 'Bob', 'grade': 10}
s2 = Student.from_dict(student_info)
s2.display_info()

In from_dict, cls is a stand-in for the Student class. This makes the method more flexible; if you were to create a subclass of Student, this factory method would correctly create instances of the subclass.


🛠️ Section 3: Static Methods - Utility Functions

A static method doesn't know anything about the class or the instance. It's essentially a regular function that is namespaced inside the class because it is logically related to it. It doesn't take self or cls as its first argument.

To create a static method, you must use the @staticmethod decorator.

Purpose: To group utility functions that have a logical connection to the class but do not depend on class or instance state.

Example: A Simple Validator Let's add a static method to our Student class to validate if a grade level is valid for the high school.

class Student:
# ... (previous methods and attributes) ...

# This is a static method
@staticmethod
def is_valid_grade(grade: int) -> bool:
"""A utility function to check if a grade is valid for this school."""
# This method has no access to 'cls' or 'self'.
# It's just a regular function living inside the class.
return 9 <= grade <= 12

# --- Let's use our static method ---

# You can call it directly on the class
if Student.is_valid_grade(11):
print("Grade 11 is a valid grade.")

if not Student.is_valid_grade(8):
print("Grade 8 is not a valid grade.")

The is_valid_grade method doesn't need to know about a specific student or the school's name; it just performs a simple, logical check related to the concept of a Student.


✨ Conclusion & Key Takeaways

Choosing the right type of method makes your class design more logical and easier to understand.

Let's summarize the key takeaways:

  • Instance Method: The default. Uses self to access and modify an object's unique state. Use this for most behaviors.
  • Class Method: Marked with @classmethod. Uses cls to access and modify the class's shared state. Often used for factory methods.
  • Static Method: Marked with @staticmethod. Has no access to self or cls. Use this for utility functions that are related to the class but independent of its state.

Challenge Yourself: Create a Calculator class.

  1. Give it an instance method add(self, x, y) that returns the sum. (This is a bit contrived, but good for practice).
  2. Give it a static method multiply(x, y) that returns the product.
  3. Give it a class attribute history = [].
  4. Give it a class method get_history(cls) that returns the history list. Modify the add method so that it appends a record of the operation to the class's history list before returning the result.

➡️ Next Steps

We've explored the different kinds of data and behaviors we can attach to a class. Next, we'll look at one of the most important principles in OOP: "Access Modifiers: public, protected, and private," where we'll learn how to control access to an object's attributes and methods.

Happy coding!