Skip to Content
Course content

118: Instance Methods, Class Methods, and Static Methods

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the most common things I see when reviewing code from developers moving into intermediate Python is the "If I don't need self, it's a static method" mentality. It sounds logical, right? If the method doesn't touch any instance-specific data, why bother with the instance? The problem is that this ignores a critical part of how object-oriented programming works: inheritance.

The Myth: "Static methods are just class methods without the cls argument"

Let's look at why this is a trap. Imagine we're building a system for a pizza shop. You want a "factory" method that lets you create a standard Margherita pizza without passing in all the toppings every time.

class Pizza:
    def __init__(self, toppings):
        self.toppings = toppings

    @staticmethod
    def create_margherita():
        # We hardcode 'Pizza' here because it's a static method
        return Pizza(["mozzarella", "basil", "tomato"])

class DeepDishPizza(Pizza):
    pass

# This works as expected
standard = Pizza.create_margherita() 

# This is where the wheels fall off
deep_dish = DeepDishPizza.create_margherita()
print(type(deep_dish)) # Output: <class '__main__.Pizza'>

Notice the problem? I called create_margherita() on the DeepDishPizza class, but I got back a base Pizza object. Because a static method knows nothing about the class it was called on, it's forced to hardcode the class name. This completely breaks polymorphism. If you're building a library or a framework, this is a nightmare for anyone trying to extend your code.

The Fix: Using @classmethod for polymorphic factories

This is where @classmethod earns its keep. Unlike a static method, a class method receives a reference to the class itself as the first argument (which we conventionally call cls). It doesn't care which specific class it is; it just knows it's the one currently being used.

class Pizza:
    def __init__(self, toppings):
        self.toppings = toppings

    @classmethod
    def create_margherita(cls):
        # cls refers to whatever class called this method
        return cls(["mozzarella", "basil", "tomato"])

class DeepDishPizza(Pizza):
    pass

deep_dish = DeepDishPizza.create_margherita()
print(type(deep_dish)) # Output: <class '__main__.DeepDishPizza'>


By using cls(...) instead of Pizza(...), the method becomes dynamic. Now, when DeepDishPizza calls the method, cls is DeepDishPizza. This is the gold standard for creating "alternative constructors" in Python.

Reserving @staticmethod for pure utility logic

So, does @staticmethod even have a purpose? Yes, but it's much narrower. Use a static method when the function logically belongs inside the class (for organization and namespaces), but it doesn't need to know anything about the class or the instance.

I usually use them for validation or helper functions. For example, checking if a topping is "legal" in our shop doesn't require knowing about a specific pizza order, nor does it require knowing about the Pizza class hierarchy.

class Pizza:
    # ... (previous code) ...

    @staticmethod
    def is_valid_topping(topping):
        # This logic is independent of class state or instance state
        allowed = ["mozzarella", "basil", "tomato", "pepperoni", "mushrooms"]
        return topping.lower() in allowed

# I can call this without even creating a Pizza object
if Pizza.is_valid_topping("pineapple"):
    print("Adding pineapple!")
else:
    print("We don't do that here.")

To wrap this up, here is my personal rule of thumb:

  • Instance Method: Use it if you need to read or change the state of a specific object (use self).
  • Class Method: Use it for factory methods or when you need to touch class-level state (use cls).
  • Static Method: Use it for utility functions that are related to the class but are essentially "standalone" functions.



📋 Practical Task

Implement a Flexible Employee Management System

You are building an HR system. You need to create an Employee class that can be instantiated in two ways: via a standard constructor (name and salary) and via a class method that parses a string (e.g., "John Doe, 50000").

Requirements:

  • Create an Employee class with an __init__ method accepting name and salary.
  • Implement a @classmethod called from_string that takes a single string argument in the format "Name, Salary", splits it, and returns a new instance of the class.
  • Implement a @staticmethod called is_valid_salary that takes a number and returns True if the salary is greater than 0, and False otherwise.
  • Create a subclass called Manager.
  • Demonstrate that calling Manager.from_string("Jane Smith, 80000") correctly returns a Manager object, not a base Employee object.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.