A class isn't just a collection of instance methods. It also has its own namespace, and Python provides a handful of decorators that attach utilities to the class itself rather than to individual instances. The most useful ones are @classmethod, @staticmethod, class-level constants, and __init_subclass__. Used well, they put related helpers right next to the class they belong to, without cluttering instance state.
@classmethod is the right choice for alternative constructors and any helper that needs to know the class but not a specific instance. The first argument cls is the class itself, so cls(...) always creates an instance of the current (possibly subclassed) class. @staticmethod is the right choice for utilities that don't need self or cls but logically belong to the class's namespace.
Class-level constants go at class scope: MAX_RETRIES = 3. They are accessible both via the class (MyClass.MAX_RETRIES) and via instances (self.MAX_RETRIES). Treat them as immutable; Python won't stop you from rebinding them, but doing so at runtime leads to confusing behavior.
Finally, __init_subclass__ is a hook that runs whenever a new class inherits from yours. It is how frameworks register plugins, enforce a pattern, or configure fields automatically without requiring the subclass author to remember a decorator. It is more advanced, but worth knowing exists.
classmethod vs staticmethod vs plain function
Use @classmethod when the function needs the class (alternative constructor, subclass-aware logic). Use @staticmethod when the function is a utility that “belongs” to the class but doesn't need access to either self or cls. Use a plain module-level function when neither applies.
A classmethod called through a subclass receives the subclass as cls, so cls(...) constructs an instance of the caller's type. That is why alternative constructors are almost always classmethods.
__init_subclass__ and registration
When a subclass is created, Python calls the base's __init_subclass__ with the new class. Frameworks use this to auto-register subclasses in a dictionary, validate that a required attribute was set, or inject defaults. The subclass author does nothing special: inheriting is enough.
Keep __init_subclass__ side effects minimal and predictable. Surprising magic in a base class makes subclasses hard to test — and harder for a future maintainer to understand.
Class-scope helpers and hooks.
| Tool | Purpose |
|---|---|
@classmethoddecorator | Receives the class as first argument (cls). |
@staticmethoddecorator | No implicit first argument. |
__init_subclass__(cls, **kw)method | Hook run for every subclass. |
__set_name__(owner, name)method | Descriptor lifecycle hook. |
typing.ClassVar[T]annotation | Marks a field as class-level (for dataclass). |
Enumclass | Perfect for class-level named constants. |
super()built-in | Dispatches to the parent classmethod/init. |
abc.ABCclass | Abstract base for required methods in subclasses. |
Using Class-Level Utilities code example
The script shows classmethods, staticmethods, a class constant, and a minimal __init_subclass__ registry.
# Lesson: Using Class-Level Utilities
class Shape:
registry: dict[str, type] = {}
default_color: str = "black"
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Shape.registry[cls.__name__] = cls
@classmethod
def lookup(cls, name: str) -> type:
return cls.registry[name]
@staticmethod
def is_valid_color(value: str) -> bool:
return isinstance(value, str) and value.isalpha()
class Circle(Shape):
def __init__(self, radius: float, color: str | None = None) -> None:
self.radius = radius
self.color = color or self.default_color
class Square(Shape):
def __init__(self, side: float) -> None:
self.side = side
print("registry :", list(Shape.registry))
print("lookup :", Shape.lookup("Circle").__name__)
c = Circle(3)
print("Circle :", c.radius, c.color)
print("valid red?", Shape.is_valid_color("red"))
print("valid 42? ", Shape.is_valid_color("42"))
# Classmethod preserves subclass identity
class Tagged(Shape):
pass
assert Shape.registry["Tagged"] is Tagged
print("tagged registered OK")
Each bit earns its keep:
1) `__init_subclass__` auto-registers Circle, Square, and Tagged in Shape.registry.
2) `lookup` is a classmethod; callers ask the base class for any subclass by name.
3) `is_valid_color` is a staticmethod, logically grouped with Shape but stateless.
4) `default_color` is a class-level constant with a sensible default per type.
Practice a classmethod factory plus a static helper.
from datetime import date, timedelta
class Receipt:
tax_rate = 0.21
def __init__(self, subtotal: float, when: date):
self.subtotal = subtotal
self.when = when
@classmethod
def today(cls, subtotal: float) -> "Receipt":
return cls(subtotal, date.today())
@staticmethod
def with_tax(amount: float, rate: float = tax_rate) -> float:
return round(amount * (1 + rate), 2)
r = Receipt.today(100)
print(r.subtotal, r.when)
print(Receipt.with_tax(100))
Check classmethod polymorphism.
class A:
@classmethod
def make(cls): return cls()
class B(A):
pass
assert isinstance(A.make(), A)
assert isinstance(B.make(), B)
Running prints:
registry : ['Circle', 'Square']
lookup : Circle
Circle : 3 black
valid red? True
valid 42? False
tagged registered OK