classmethod vs staticmethod in Python
Compare classmethod and staticmethod in Python with examples.
TL;DR
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.
Why these decorators exist
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:
- Instance method (default): binds the instance → first parameter is
self. - Class method: binds the class → first parameter is
cls. - Static method: binds nothing → no implicit first parameter.
This binding behavior is the core mental model for choosing the right decorator.
Quick definitions
-
@classmethod- First parameter is
cls(the actual class used to call the method). - Sees class attributes, can create/return instances, and supports inheritance and polymorphism naturally.
- First parameter is
-
@staticmethod- No
selforclsinjected. - Pure helper function placed on a class for organization or discoverability.
- No
Side-by-side cheat sheet
| 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) |
When to use @classmethod
1) Alternate constructors
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)
2) Polymorphic factories
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'>
3) Class-level configuration and registries
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]
4) Versioned/typed constructors with 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(...)
When to use @staticmethod
1) Pure helpers that don’t touch class/instance state
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()
2) Validation or small transforms used by multiple constructors
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.
Common pitfalls & how to avoid them
-
Needing
clsbut using@staticmethod- If you might return
cls(...)(alternate constructor) or read class attributes, use@classmethod.
- If you might return
-
Using
@classmethodjust for grouping- If you never touch
cls, you’re likely better off with a@staticmethod(or a top-level function).
- If you never touch
-
Confusing inheritance behavior
-
@classmethodparticipates in polymorphism naturally. -
@staticmethodstill 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/@staticmethodabove@abstractmethod.)
-
-
No built-in
@classproperty- Python has
@propertyfor instances and@classmethodfor 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@classmethodlikedef info(cls)is clearer.
- Python has
Design patterns you’ll meet in the wild
- Named constructors:
Path.home(),datetime.fromtimestamp(...) - Format decoders:
Message.from_json(...),Model.from_pretrained(...) - Registries/plugins:
Handler.register(...),Handler.create(name) - Utility namespaces:
Bytes.to_hex(...),Password.hash(...)
Testing tips
-
For
@classmethodalternate constructors, test both base class and subclass calls to ensure polymorphism:-
Base.from_x(...)returnsBase -
Sub.from_x(...)returnsSub
-
-
For
@staticmethod, treat as pure functions:- No fixtures for instances/classes needed.
- Fast, deterministic unit tests.
Decision guide
- Does the method need the instance? → Plain method (no decorator).
- Does the method need the class (to read class data, construct, or be polymorphic)? →
@classmethod. - Neither? →
@staticmethod(or module-level function if discoverability isn’t a concern).
Mini FAQ
-
“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
@staticmethodfaster?” 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 iscls. Often it’s simpler to cache on the class (e.g.,cls._cache[...]).
Takeaways
- Reach for
@classmethodto express type-aware behavior: alternate constructors, polymorphic factories, class-level configuration. - Use
@staticmethodfor pure utilities that are best colocated with a class. - If neither binding helps, a module-level function is often the cleanest choice.
Happy Pythoning! 🐍