Class and Static Methods
Instance methods receive the instance (self); the other two flavors receive less. A @classmethod receives the class (cls) - the right tool for alternative constructors that must respect inheritance. A @staticmethod receives nothing - a namespaced helper that lives in the class's namespace without touching either the instance or the class. Choosing among the three is an API design decision with a clear test: what does the method actually need?
Class Methods: Alternative Constructors
class Date:
def __init__(self, year, month, day):
self.year, self.month, self.day = year, month, day
@classmethod
def from_string(cls, s):
# cls is the ACTUAL class called - subclasses inherit working factories
y, m, d = map(int, s.split("-"))
return cls(y, m, d)
@classmethod
def today(cls):
import datetime
now = datetime.date.today()
return cls(now.year, now.month, now.day)
def __repr__(self):
return f"{type(self).__name__}({self.year}, {self.month}, {self.day})"
class BusinessDate(Date):
pass
d = Date.from_string("2026-11-07")
print(d) # Date(2026, 11, 7)
b = BusinessDate.from_string("2026-11-07")
print(b) # BusinessDate(2026, 11, 7) - cls did its job
print(isinstance(b, Date)) # True
@classmethod binds the class as the first argument instead of an instance. The canonical use is a second (or third) constructor: Date.from_string and Date.today build a Date from inputs that are not the canonical (year, month, day) tuple. The critical detail is cls, not a hard-coded name: BusinessDate.from_string(...) passes BusinessDate as cls, so the factory returns the right subtype - a hard-coded return Date(...) would silently downgrade a subclass call. This is exactly why the factory must be a classmethod and not a staticmethod or a module function: it has to see which class was actually invoked.
Static Methods: Namespaced Helpers
class Color:
def __init__(self, r, g, b):
self.r, self.g, self.b = r, g, b
@staticmethod
def from_hex(hexstr):
# needs neither an instance nor the class - a pure helper
hexstr = hexstr.lstrip("#")
return int(hexstr[0:2], 16), int(hexstr[2:4], 16), int(hexstr[4:6], 16)
@classmethod
def from_hex_obj(cls, hexstr):
r, g, b = cls.from_hex(hexstr) # a classmethod can call a staticmethod
return cls(r, g, b)
def __repr__(self):
return f"Color({self.r}, {self.g}, {self.b})"
print(Color.from_hex("1a2b3c")) # (26, 43, 60) - a plain function call
print(Color.from_hex_obj("1a2b3c")) # Color(26, 43, 60) - the object
A @staticmethod is a plain function that happens to be filed under the class. Use it when the helper is genuinely independent of both the instance and the class - parsing a hex string, a small computation, a format routine. The question to ask is always "does this need self or cls?" - if the honest answer is no, a staticmethod (or better, a module-level private function) is the truthful form. A common evolution: start with a staticmethod, realize it needs to construct the right subtype, and promote it to a classmethod - the from_hex_obj above shows the two working together, the static doing the parsing, the class doing the construction.
The Three Flavors Compared
class Demo:
class_attr = "on the class"
def instance_method(self):
return f"self={self!r}, cls={type(self).__name__}"
@classmethod
def class_method(cls):
return f"cls={cls.__name__}, attr={cls.class_attr}"
@staticmethod
def static_method():
return "no self, no cls"
d = Demo()
print(d.instance_method()) # bound: instance pre-filled
print(Demo.class_method()) # bound: class pre-filled
print(d.static_method()) # plain call, no binding
| Flavor | First arg | Use when |
|---|---|---|
| instance method | self | it reads or mutates instance state |
@classmethod | cls | it constructs or queries the class; must respect subtypes (factories) |
@staticmethod | none | it is a helper that needs neither; pure function in a namespace |
Decision Rules and Pitfalls
- Factories that return instances: classmethod. If the method's job is "make one of us," it must see
clsso subclasses work. A factory that hard-codes its own class name is a latent bug waiting for a subclass. - Helpers that never touch the class: staticmethod or module function. Prefer a module-level function if nothing about it is class-specific; a staticmethod is the choice when the name belongs in the class's public API (
Color.from_hexis aColorthing, not a module thing). - A staticmethod that reaches for the class name is a classmethod in disguise. The moment the body references its own class, the binding is lying - convert it.
- Do not use
@staticmethodto "group utilities." A class that is only staticmethods is a namespace with inheritance - use a module or, for a real family of related operations, a dataclass of pure functions. The class should exist because instances of it are a thing.
Summary
Three flavors, one test - what does the method need? self means instance method; the class, especially for construction, means @classmethod with cls (the alternative-constructor pattern, where cls - not a hard-coded name - is what keeps subclass factories correct); neither means @staticmethod, a pure helper filed under the class's namespace. The smells are the inversions: a factory that hard-codes its class (should be a classmethod), a staticmethod that secretly references its own class (should be a classmethod), and a class that exists only to hold staticmethods (should be a module). Get the binding honest and the API tells the truth about where the behavior lives.