Compare classmethod and staticmethod in Python with examples.
Use
@classmethod
when the method needs to know which class it belongs to (e.g., alternate constructors, polymorphic factories, class-wide configuration). Use@staticmethod
when you just want a helper function namespaced under a class and it doesn’t need instance (self
) or class (cls
) access.
Python methods are just functions living on a class. When you access them, Python’s descriptor protocol decides what gets bound as the first argument:
self
.cls
.This binding behavior is the core mental model for choosing the right decorator.
@classmethod
cls
(the actual class used to call the method).@staticmethod
self
or cls
injected.Feature | Instance method | Class method | Static method |
---|---|---|---|
First arg | self | cls | (none) |
Reads instance state | ✅ | ❌ | ❌ |
Reads/changes class state | ⚠️ via self.__class__ | ✅ | ❌ |
Alternate constructors | ❌ | ✅ | ❌ |
Polymorphic dispatch by subclass | ✅ | ✅ | ⚠️ (lookup works, but no binding) |
Good for utilities | ❌ | ⚠️ | ✅ |
Testability | Good | Good | Excellent (pure) |
@classmethod
Give readers an obvious way to build instances from other representations.
class User:
def __init__(self, name, is_admin=False):
self.name = name
self.is_admin = is_admin
@classmethod
def from_env(cls):
import os
return cls(os.getenv("USER", "anonymous"))
@classmethod
def admin(cls, name):
return cls(name, is_admin=True)
Subclasses inherit and can override classmethods. Calls via the subclass bind cls
to the subclass—so your factory keeps returning the right type.
class User:
def __init__(self, name):
self.name = name
@classmethod
def from_dict(cls, data):
return cls(data["name"])
Now subclasses can reuse it and automatically return their own type:
class Admin(User):
pass
user = User.from_dict({"name": "Alice"})
admin = Admin.from_dict({"name": "Bob"})
print(type(user)) # <class '__main__.User'>
print(type(admin)) # <class '__main__.Admin'>
Keep shared, type-specific state on the class.
class Service:
_registry = {}
@classmethod
def register(cls, name, impl):
cls._registry[name] = impl
@classmethod
def get(cls, name):
return cls._registry[name]
typing.Self
From Python 3.11, use Self
to signal “returns an instance of the calling class”.
from typing import Self
class Config:
@classmethod
def load(cls, path: str) -> Self:
...
return cls(...)
@staticmethod
Prefer module-level functions for true utilities, unless colocating them with the class improves discoverability or API ergonomics.
class Password:
@staticmethod
def hash(plain: str) -> str:
import hashlib
return hashlib.sha256(plain.encode()).hexdigest()
Keep related helpers near where they’re used.
class Color:
@staticmethod
def _clamp(x: int) -> int:
return max(0, min(255, x))
Rule of thumb: If the function could live at module scope without losing clarity, a
@staticmethod
is fine; if it benefits from knowing the class, make it a@classmethod
.
Needing cls
but using @staticmethod
cls(...)
(alternate constructor) or read class attributes, use @classmethod
.Using @classmethod
just for grouping
cls
, you’re likely better off with a @staticmethod
(or a top-level function).Confusing inheritance behavior
@classmethod
participates in polymorphism naturally.@staticmethod
still follows normal attribute lookup (so you can override it), but it doesn’t get the class bound automatically.Decorator order with ABCs
With abc.ABC
, combine as:
from abc import ABC, abstractmethod
class Repo(ABC):
@classmethod
@abstractmethod
def from_url(cls, url: str): ...
@staticmethod
@abstractmethod
def validate(url: str) -> bool: ...
(Order matters: put @classmethod
/ @staticmethod
above @abstractmethod
.)
No built-in @classproperty
@property
for instances and @classmethod
for methods. If you want a computed class-level property, you’ll need a small custom descriptor or a metaclass. Most of the time, a simple @classmethod
like def info(cls)
is clearer.Path.home()
, datetime.fromtimestamp(...)
Message.from_json(...)
, Model.from_pretrained(...)
Handler.register(...)
, Handler.create(name)
Bytes.to_hex(...)
, Password.hash(...)
For @classmethod
alternate constructors, test both base class and subclass calls to ensure polymorphism:
Base.from_x(...)
returns Base
Sub.from_x(...)
returns Sub
For @staticmethod
, treat as pure functions:
@classmethod
.@staticmethod
(or module-level function if discoverability isn’t a concern).“Can a staticmethod access class variables?” Not implicitly. You’d have to reference the class by name (which breaks subclassing) — usually a smell. Prefer @classmethod
.
“Is @staticmethod
faster?” It avoids binding overhead, but that cost is tiny. Choose for API clarity, not micro-perf.
“Can I cache a classmethod?” You can wrap the underlying function with functools.cache
/lru_cache
, but mind that the first arg is cls
. Often it’s simpler to cache on the class (e.g., cls._cache[...]
).
@classmethod
to express type-aware behavior: alternate constructors, polymorphic factories, class-level configuration.@staticmethod
for pure utilities that are best colocated with a class.Happy Pythoning! 🐍