skip to content

Instance, Class and Static Methods

A plain method binds self, @classmethod binds cls — the idiom for alternative constructors — and @staticmethod binds nothing. Interviewers ask which to reach for, then how that binding actually happens.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

3

How do a plain method, a @classmethod and a @staticmethod differ in Python?

level: juniorimportance: must knowfreq 82%

answer

  1. Three kinds of callable in a class body
  2. What each one receives implicitly
  3. self, cls, or nothing at all
  4. Alternative constructors need the class
  5. Cls.m() versus obj.m()

basics

~20 s

A plain method takes the instance as its first argument, self. A classmethod takes the class as cls, so a subclass gets its own class. A staticmethod receives nothing implicitly; it is just a function stored in the class namespace.

solid answer

~40 s

Three kinds of callable can live in a class body, and they differ only in what Python passes implicitly. `def m(self)` binds the instance: `obj.m()` passes `obj` as `self`. `@classmethod def m(cls)` binds the class: both `obj.m()` and `Cls.m()` pass a class, and a subclass passes *its own* class, which is why alternative constructors such as `dict.fromkeys` are classmethods. `@staticmethod def m()` gets nothing implicit; it is an ordinary function namespaced under the class, useful for grouping a helper that needs neither instance nor class. Default to the instance method, use a classmethod when you need the class (usually to build one), and a staticmethod when you need neither but the helper still belongs to the type.

code

python · 26 lines
python
class Batch:
    kind = "generic"

    def __init__(self, rows):
        self.rows = rows

    def size(self):                       # instance method: needs self
        return len(self.rows)

    @classmethod
    def empty(cls):                       # class method: needs the class
        return cls([])

    @staticmethod
    def is_valid_row(row):                # static method: needs neither
        return "id" in row


class FlagBatch(Batch):
    kind = "flags"


print(Batch([1, 2]).size())                  # 2
print(type(FlagBatch.empty()).__name__)      # FlagBatch
print(Batch.is_valid_row({"id": 1}))         # True
print(FlagBatch.empty().kind)                # flags

go deeper

for a junior

Be ready to state, without hesitating, what each of the three receives: the instance, the class, or nothing. Know that self is a parameter name and not a keyword, and be able to write a small class using all three.

for a middle

Explain the mechanics: attribute lookup produces a bound method for a plain method and for a classmethod, and a plain function for a staticmethod. Show why cls follows the subclass, and name a stdlib alternative constructor.

for a senior

Demonstrate judgement about which to reach for in real code, including when a module-level function beats a staticmethod, and how a hardcoded class name in a factory quietly breaks every subclass in a hierarchy.

for a principal

Own the API-design angle: how many named constructors a type should expose before it becomes a builder, whether a helper belongs to the type at all, and how these choices constrain subclassing across a codebase your team maintains.

### Only one thing actually differs Every `def` written in a class body creates an ordinary function object that is stored in the class's `__dict__`. `@classmethod` and `@staticmethod` do not rewrite that function; they wrap it in a different kind of class attribute, and the wrapper decides **what Python passes implicitly when the name is looked up and then called**. That single difference is the whole distinction, and everything else follows from it. ### Plain method: `def m(self)` `obj.m()` passes `obj` as the first positional argument. `self` is not a keyword — it is only the conventional name of the first parameter; renaming it works and reads badly. Looking the same name up on the class instead gives back the plain function, so `Cls.m(obj)` does exactly the work that `obj.m()` does. Reach for a plain method whenever the body reads or mutates per-instance state, which is the overwhelming majority of methods: "should this be an instance method?" is the question you argue your way *out* of, not into. ### `@classmethod`: `def m(cls)` The first argument is the class. Both `Cls.m()` and `obj.m()` work, and both pass a class — for `obj.m()` it is `type(obj)`, and the instance itself is discarded, so a classmethod cannot see instance attributes. The decisive property is that `cls` is bound at call time to the class the attribute was looked up on, not to the class where the `def` appeared. A subclass therefore gets *itself* with no override: ```python class Base: @classmethod def make(cls): return cls() class Child(Base): pass type(Child.make()).__name__ # 'Child' ``` That is why alternative constructors are classmethods. The standard library uses the idiom everywhere: `dict.fromkeys`, `datetime.date.today`, `datetime.date.fromisoformat`, `datetime.datetime.fromtimestamp`. The second, less common family is class-level operations that touch class state rather than instance state — a registry, a shared counter, a configured default. ### `@staticmethod`: `def m()` No implicit argument at all. Looking the name up on the class or on an instance both hand back the plain function, and calling it passes exactly the arguments written at the call site. The honest question is "why not a module-level function?", and the honest answers are about organisation rather than mechanics: the helper is meaningful only in the context of this class, readers look for it there, and because it is still a class attribute a subclass can override it while lookup continues to go through the MRO. When none of that applies, a module-level function is simpler to import and to test. What is *not* a reason is speed — the difference is negligible and no method kind should be chosen on that basis. ### What the call site actually sees | lookup | plain method | `@classmethod` | `@staticmethod` | |---|---|---|---| | `Cls.m` | the plain function | bound method, bound to the class | the plain function | | `obj.m` | bound method, bound to `obj` | bound method, bound to `type(obj)` | the plain function | | `Cls.m()` | `TypeError`, `self` missing | works | works | | `obj.m()` | works | works, instance discarded | works | `inspect.ismethod` is `True` for exactly the bound cases; `inspect.isfunction` is `True` for the unbound ones. That pair is the quickest way to check what a decorator actually produced. ### Choosing, in one pass Does the body need the instance? Instance method. Does it need the class — usually to construct one, occasionally to touch class-level state? `@classmethod`. Does it need neither, but does it still belong to this type conceptually? `@staticmethod`. Does it need neither and is it not really about the type? A module-level function. ### The traps interviewers probe - Omitting `self` from the parameter list and then being baffled by `TypeError: C.m() takes 0 positional arguments but 1 was given`. - Writing `@staticmethod` over a `def m(self)`: `self` becomes an ordinary parameter, and the first argument passed lands in it with no complaint until something breaks downstream. - Hardcoding the class name inside a classmethod — `return Batch(...)` instead of `return cls(...)` — which silently returns the base type for every subclass. - Expecting a classmethod called through an instance to reach instance attributes. It receives only the class. - Decorator ordering: `@classmethod` and `@staticmethod` belong outermost, applied last, above any other decorator on the method. ### Version note The three kinds have existed since Python 2.2 and behave identically on 3.14. One modern wrinkle worth knowing: since 3.10 a `staticmethod` object is itself callable, so pulling it straight out of the class `__dict__` and calling it works, where 3.9 and earlier raised `TypeError`. Since the same release both wrappers copy `__name__`, `__qualname__` and `__doc__` from the function and expose it as `__wrapped__`.

  • What happens if you call an instance method through the class with no argument, as in Batch.size()?
    It raises `TypeError: Batch.size() missing 1 required positional argument: 'self'`. Looking the name up on the class hands back the plain function rather than a bound method, so nothing fills `self` in. `Batch.size(obj)` works and is exactly the call that `obj.size()` performs for you.
  • Why use a @staticmethod at all instead of a module-level function?
    For namespacing and discoverability: the helper is meaningful only alongside this class, readers look for it there, and because it stays a class attribute a subclass can override it and lookup still goes through the MRO. There is no performance argument worth making. If nothing about the class is implied, a module-level function is the simpler choice.
  • Can a @classmethod or a @staticmethod be overridden in a subclass?
    Yes — both are ordinary class attributes, so a subclass redefining the name shadows it through the MRO. A classmethod additionally picks up the subclass in `cls` without being redefined at all, which is precisely what makes the alternative-constructor idiom work across a hierarchy.

Think of the class as a factory blueprint: an instance method works on one finished product, a classmethod works on the blueprint itself and can stamp out new products, and a staticmethod is a tool kept in the factory's drawer that touches neither.

saying these in an interview costs you the question

  • Says self is a Python keyword rather than a parameter name
  • Claims @staticmethod exists mainly for performance
  • Thinks a @classmethod receives the instance as well as the class
  • Hardcodes the class name inside a classmethod instead of using cls
  • Believes an instance method can be called as Cls.m() with no argument

context

open as a page

Why is @classmethod the idiom for alternative constructors in Python?

level: middleimportance: should knowfreq 58%

basics

~20 s

Because a classmethod receives the actual class as cls, so returning cls(...) builds the right type even when a subclass calls it. A factory that names the class directly hardcodes it and silently breaks inheritance.

open as a page

Why does a callback registry holding obj.method keep that instance alive in Python?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Looking up obj.method builds a bound method object holding the instance in self and the underlying function in func. That is an ordinary strong reference, so whatever stores the bound method keeps the instance alive.

open as a page