The difference between @staticmethod and @classmethod in Python

The mechanical difference is one line: a classmethod receives the class, a staticmethod receives nothing. Why that matters only becomes visible when somebody subclasses your class, which is where this article spends its time.

What each one receives

class Demo:
    def instance_method(self, x): ...
    @classmethod
    def class_method(cls, x): ...
    @staticmethod
    def static_method(x): ...
instance_method got self=Demo, x=1
class_method    got cls=Demo, x=1
static_method   got nothing but x=1

classmethod and staticmethod can both be called on the class directly. An instance method cannot:

Demo.instance_method(2) -> TypeError: Demo.instance_method() missing 1 required positional argument: 'x'

Because self was never supplied — Demo.instance_method is just a function, and you would have to pass an instance yourself.

The whole argument, in two lines

Here is why the choice matters. Two factory methods on a base class, one of each kind:

class Base:
    @classmethod
    def make_cls(cls):
        return cls()
    @staticmethod
    def make_static():
        return Base()

class Child(Base):
    pass
Child.make_cls()    -> Child
Child.make_static() -> Base   <- hardcoded Base

cls is whatever class the call came through, so the classmethod builds a Child. The staticmethod names Base in its body and therefore always returns a Base, no matter who calls it.

That is the entire case for classmethod, and it is why alternative constructors must be classmethods:

class Point:
    @classmethod
    def from_string(cls, text):
        x, y = (int(p) for p in text.split(","))
        return cls(x, y)
Point.from_string('1,2')   : Point(1, 2)
Point3D.from_string('3,4') : Point3D(3, 4)   <- correct subclass

Write return Point(x, y) instead of return cls(x, y) and every subclass silently gets the wrong type back. It is the same mistake as new self() versus new static() in PHP, if you have met that one.

cls also picks up overridden attributes

Not just the type — anything the subclass changed:

class Animal:
    sound = "..."
    @classmethod
    def describe(cls):
        return f"{cls.__name__} says {cls.sound}"
Dog.describe()        : Dog says woof
Dog.describe_static() : Animal says ...   <- still the base class

So a classmethod is the right choice whenever the behaviour should follow class-level configuration that subclasses are expected to override.

When a staticmethod is the right answer

When the function genuinely needs neither. It is a helper that belongs to the class for readability and namespacing, and nothing more:

class Temperature:
    @staticmethod
    def c_to_f(celsius):
        return celsius * 9 / 5 + 32

No self, no cls, no class attributes. If you find yourself wanting either, you wanted a classmethod.

The honest alternative is a module-level function. A staticmethod buys you grouping and the ability to override it in a subclass; if neither matters, a plain function is simpler.

What these things actually are

type(Demo.static_method)   : function
type(Demo.class_method)    : method
type(Demo.instance_method) : function
type(d.instance_method)    : method   <- bound

A staticmethod is a plain function that happens to live in a class namespace — which is exactly what it looks like above. A classmethod is already bound to the class, so it shows up as a method even when reached through the class. And an instance method is a function on the class but a bound method on an instance, which is the descriptor protocol doing its work.

Since Python 3.10, staticmethod objects are directly callable, so you can call one from inside the class body during definition — which used to be a small annoyance.

The mistake that survives

Forget the decorator and the failure is delayed:

class Oops:
    def helper(x):
        return x * 2
Oops.helper(5)   : 10   <- works when called on the CLASS
Oops().helper(5) -> TypeError: Oops.helper() takes 1 positional argument but 2 were given

Called on the class it works, because nothing is bound and 5 lands in x. Called on an instance, the instance is passed as the first argument, x becomes the object, and your real argument has nowhere to go.

The error message is confusing the first time — you passed one argument and it says you passed two. The invisible one is self.

One practical note

@staticmethod and @classmethod go outermost, on top of any other decorators:

class Ordered:
    @staticmethod
    def right():
        return "ok"

They wrap the function in a descriptor, so anything applied above them would be decorating the descriptor rather than the function — which almost never does what you want.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).