classmethod vs staticmethod in Python

Compare classmethod and staticmethod in Python with examples.

TL;DR

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.

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.
  • @staticmethod

    • No self or cls injected.
    • Pure helper function placed on a class for organization or discoverability.

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 @staticmethod is fine; if it benefits from knowing the class, make it a @classmethod.

Common pitfalls & how to avoid them

  1. Needing cls but using @staticmethod

    • If you might return cls(...) (alternate constructor) or read class attributes, use @classmethod.
  2. Using @classmethod just for grouping

    • If you never touch cls, you’re likely better off with a @staticmethod (or a top-level function).
  3. 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.
  4. 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.)

  5. No built-in @classproperty

    • Python has @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.

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 @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:

    • No fixtures for instances/classes needed.
    • Fast, deterministic unit tests.

Decision guide

  1. Does the method need the instance? → Plain method (no decorator).
  2. Does the method need the class (to read class data, construct, or be polymorphic)?@classmethod.
  3. 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 @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[...]).

Takeaways

  • Reach for @classmethod to express type-aware behavior: alternate constructors, polymorphic factories, class-level configuration.
  • Use @staticmethod for pure utilities that are best colocated with a class.
  • If neither binding helps, a module-level function is often the cleanest choice.

Happy Pythoning! 🐍