Instance methods receive self, class methods receive cls, and static methods receive neither automatically. The right choice reflects what the behavior actually depends on.
An alternate constructor
from dataclasses import dataclass
from datetime import date
@dataclass(frozen=True)
class Subscription:
started_at: date
@classmethod
def from_iso(cls, value: str) -> "Subscription":
return cls(started_at=date.fromisoformat(value))
@staticmethod
def accepted_format() -> str:
return "YYYY-MM-DD"
Calling cls(...) preserves subclass construction, unlike hard-coding Subscription(...). A staticmethod fits when an operation belongs to the class API conceptually but requires no state.
A practical decision rule
Use an instance method for object behavior. Use classmethod for factories that construct the concrete class or read class-level configuration. Consider a module function before staticmethod, especially when several types can reuse the logic.
Do not use decorators to hide global dependencies. Pass collaborators explicitly and keep complex parsing outside a model when it has a separate responsibility. See Python dataclasses and the object-oriented Python guide.
How binding works
Functions in a class body implement the descriptor protocol. Accessing an instance method through an object creates a bound method and supplies that object first. Accessing it through the class requires an explicit instance:
class Counter:
def increment(self, value: int) -> int:
return value + 1
counter = Counter()
assert counter.increment(2) == 3
assert Counter.increment(counter, 2) == 3
classmethod changes binding so Python supplies the class used for access. staticmethod disables automatic binding and returns the underlying function without adding an argument. The names self and cls are conventions rather than keywords, but following them makes intent immediately clear.
classmethod and inheritance
The value of cls becomes obvious with subclasses. A factory should construct the type through which it was called:
@dataclass(frozen=True)
class AnnualSubscription(Subscription):
discount: int = 10
plan = AnnualSubscription.from_iso("2026-09-01")
assert isinstance(plan, AnnualSubscription)
This works because the method calls cls(...). Hard-coding Subscription(...) would always return the base. The factory still has to match subclass constructors. Adding incompatible required fields may break inherited factories. Override the factory, provide suitable defaults, or use an external service when constructors diverge.
Class methods can also read attributes from the concrete class:
class Importer:
separator = ","
@classmethod
def split(cls, line: str) -> list[str]:
return line.split(cls.separator)
class TsvImporter(Importer):
separator = "\t"
TsvImporter.split(...) uses tabs without duplicating logic. This suits stable per-subtype configuration. Values that change per environment or request should be injected rather than stored as mutable global class state.
Designing alternate constructors
A descriptive factory name states its input representation: from_json, from_csv_row, or from_config. It should validate input and return a valid object. If it performs I/O, caching, networking, or substantial orchestration, a separate function or service class usually has a clearer responsibility.
Annotate the return as the current type. On modern Python, typing.Self says that the result follows the concrete class:
from typing import Self
class User:
def __init__(self, name: str) -> None:
self.name = name
@classmethod
def from_text(cls, text: str) -> Self:
name = text.strip()
if not name:
raise ValueError("empty name")
return cls(name)
For versions before Python 3.11, use a bound type variable or forward reference according to project compatibility. An annotation does not replace input validation.
When staticmethod fits
A static method can keep a small operation close to the type when it clearly belongs to that type's vocabulary, such as validating a format used nowhere else. It also prevents accidental method binding for a function stored in the class body.
class ProductCode:
@staticmethod
def normalize(value: str) -> str:
return value.strip().upper()
If normalize becomes useful to customers, orders, and importers, moving it to a shared module makes dependency and reuse clearer. A static method gets no special access to private attributes, is not inherently faster, and creates no state isolation.
Overriding and super()
Instance and class methods participate naturally in polymorphism. An overridden class method receives the subclass and may delegate through super(). Static methods can be overridden too, but a call through a fixed class name may bypass the subclass implementation. If polymorphic variation matters, a class or instance method usually expresses that intent better.
Avoid changing method kind in an override, such as replacing an instance method with a static method. Even when some calls happen to work, the contract becomes surprising to readers, subclasses, and type checkers.
Tests and common mistakes
Test alternate constructors with valid input, boundary cases, and parsing failures. Include a subclass when preserving inheritance is part of the promise. Test pure static methods by input and output just like module functions.
Do not use a class method as a disguise for mutable global state. Changing class attributes in tests can leak across cases and create difficult concurrency bugs. Do not choose staticmethod merely because the current body does not use self; the behavior may belong to an object and later need state, or it may belong at module level. Decide from the public responsibility, not the body's current shape.
Inspection and equivalent calls
Looking at attributes reinforces the distinction. Subscription.from_iso is already a method bound to the class, while Subscription.accepted_format is the static function exposed through its namespace. In the class's raw dictionary, before descriptor access, the values are classmethod and staticmethod objects.
Calling a static method through an instance is allowed, but often hides its independence. Prefer ProductCode.normalize(value) to show that no object participates. Calling a class method through an instance is also valid, yet Type.factory(...) more clearly announces that a new object will be built.
Decorator order matters when they are combined. @classmethod wraps the function and normally appears as the outer decorator. Avoid clever combinations with property; explicit APIs supported by type checkers are easier to maintain.
Before choosing, ask one question: does the result depend on the receiving object, the receiving class, or neither? The answers point to an instance method, classmethod, or an independent function. Reserve staticmethod for cases where the class namespace genuinely adds meaning.
The official classmethod and staticmethod documentation, accessed July 22, 2026, explains binding and inheritance. A small predictable API matters more than placing every related helper inside a class.