Python classes have three kinds of methods. The only difference between them is what they receive as the first argument — and that tells us a lot about when to use each.
Instance Methods (Regular Methods)
These are the most common. They take self as the first argument, which gives them access to the instance and all its attributes.
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def describe(self): # instance method — needs self
return f"{self.size} pizza with {', '.join(self.toppings)}"
p = Pizza("Large", ["mushrooms", "olives"])
print(p.describe()) # Large pizza with mushrooms, olives
We use instance methods whenever the logic needs to read or modify the object’s state.
Class Methods (@classmethod)
These take cls as the first argument instead of self. They receive the class itself, not an instance. The most common use case is alternative constructors (factory methods).
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
@classmethod
def margherita(cls, size): # cls = Pizza (or a subclass)
return cls(size, ["mozzarella", "basil", "tomato"])
p = Pizza.margherita("Medium") # alternative way to create a Pizza
print(p.toppings) # ['mozzarella', 'basil', 'tomato']
The beauty of cls is that it respects inheritance. If a subclass calls margherita(), cls will be the subclass, not Pizza.
Static Methods (@staticmethod)
These don’t receive self or cls. They’re just regular functions that live inside the class for organizational purposes.
class Pizza:
@staticmethod
def validate_topping(topping): # no self, no cls
valid = ["mushrooms", "olives", "pepperoni", "mozzarella"]
return topping.lower() in valid
print(Pizza.validate_topping("Olives")) # True
We use static methods for utility logic that’s related to the class but doesn’t need access to any instance or class state.
Side-by-Side Comparison
Access: instance + class
Use: most business logic
Access: class only
Use: factory methods
Access: nothing
Use: utility helpers
Why @classmethod Matters for Inheritance
Here’s the real power. When we subclass, cls points to the subclass, so factory methods automatically return the correct type.
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
@classmethod
def margherita(cls, size):
return cls(size, ["mozzarella", "basil"]) # cls = whatever subclass
class DeepDish(Pizza):
pass
dd = DeepDish.margherita("Large")
print(type(dd)) # <class 'DeepDish'> — not Pizza!
If we’d used a @staticmethod and hardcoded Pizza(...), the subclass would have gotten a Pizza back instead of a DeepDish. That’s a subtle but important difference.
The Common Interview Question
“What’s the difference between
@staticmethodand@classmethod?”
The short answer: @classmethod receives the class as cls and can create instances or access class-level state. @staticmethod receives nothing extra — it’s just a plain function living inside the class namespace. If the method doesn’t need access to the class or instance at all, use @staticmethod. If it needs the class (especially for creating objects), use @classmethod.
Calling Convention
All three can be called on the class or an instance, but the conventions are:
Pizza.validate_topping("olives") # static — usually called on class
Pizza.margherita("Small") # classmethod — usually called on class
p = Pizza("Large", ["olives"])
p.describe() # instance method — called on instance
In simple language, the three method types differ by what they get as a first argument: self (the object), cls (the class), or nothing. Pick the one that matches what information the method actually needs.