Skip to content

@staticmethod vs @classmethod

Both decorators define a method that can be called on the class itself (not just an instance), but they differ in what gets passed automatically: @classmethod receives the class (cls) as its first argument; @staticmethod receives nothing automatic at all โ€” itโ€™s just a regular function that happens to live inside the classโ€™s namespace.

Without either decorator, every method is an instance method โ€” it requires an instance and automatically receives self. Calling it on the class instead of an instance, or needing access to the class rather than a specific instance, doesnโ€™t work cleanly:

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 {self.toppings}"
# To build a "constructor-like" helper, you'd have to hack around needing an instance
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def describe(self): # instance method: needs an instance, gets `self`
return f"{self.size} pizza with {self.toppings}"
@classmethod
def margherita(cls): # alternative constructor โ€” gets `cls`
return cls(size="medium", toppings=["tomato", "mozzarella"])
@staticmethod
def is_valid_size(size): # no self, no cls โ€” just a namespaced utility
return size in ("small", "medium", "large")
p = Pizza.margherita() # called on the class, builds an instance via cls(...)
print(p.describe()) # medium pizza with ['tomato', 'mozzarella']
print(Pizza.is_valid_size("large")) # True -- no instance needed at all
  • @classmethod for alternative constructors: Pizza.margherita() reads as a named, discoverable way to build an instance, and โ€” critically โ€” cls(...) means subclasses inherit the alternative constructor correctly (Pizza.margherita() called on a subclass builds a subclass instance, not a base-class one).
  • @staticmethod for logically-grouped utilities: is_valid_size doesnโ€™t need self or cls at all; making it a plain module-level function would work too, but keeping it on the class groups related functionality and namespaces it (Pizza.is_valid_size(...)).
  • Clear intent at the call site: seeing @classmethod/@staticmethod tells a reader immediately whether the method needs instance state, class state, or neither.
  • Calling any of the three (instance method, @classmethod, @staticmethod) works whether you call it via an instance or the class โ€” Python resolves self/cls automatically based on the decorator, not based on how you called it.
  • The key practical difference from a plain module-level function: @staticmethod lives in the classโ€™s namespace (Pizza.is_valid_size) and gets inherited by subclasses, but doesnโ€™t participate in polymorphism the way @classmethod does.
  • A @classmethod alternative constructor called on a subclass correctly returns an instance of the subclass, because cls is whichever class the method was actually accessed through:
class StuffedCrustPizza(Pizza):
pass
p = StuffedCrustPizza.margherita()
print(type(p)) # <class '__main__.StuffedCrustPizza'> -- not Pizza!
  1. What does a @classmethod receive automatically as its first argument?

    Answer`cls` โ€” the class the method was called through (which may be a subclass, not necessarily the class where the method was defined).
  2. Why is @classmethod preferred over @staticmethod for alternative constructors?

    AnswerBecause `cls(...)` inside the method correctly builds an instance of whatever class it was actually called on โ€” including subclasses โ€” while a `@staticmethod` would have to hardcode the class name, breaking subclassing.
  3. What does @staticmethod receive automatically?

    AnswerNothing โ€” no `self`, no `cls`. It behaves like a plain function, just namespaced under the class.