Compare classmethod and staticmethod in Python with examples.
Use
@classmethodwhen the method needs to know which class it belongs to (e.g., alternate constructors, polymorphic factories, class-wide configuration). Use@staticmethodwhen 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
@staticmethodis 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! 🐍