Skillversitas

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
FlavorFirst argUse when
instance methodselfit reads or mutates instance state
@classmethodclsit constructs or queries the class; must respect subtypes (factories)
@staticmethodnoneit 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 cls so 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_hex is a Color thing, 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 @staticmethod to "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.