2.1.4OOP Fundamentals

Instance methods, class methods (`@classmethod`), static methods (`@staticmethod`)

1,951 words9 min readdifficulty · medium

WHY do we even need three kinds?


WHAT each one is


HOW the binding actually works (derivation from scratch)

We will derive why self "appears automatically", instead of memorising it.

So all three are the same mechanism with different "what do I inject" rules.

Figure — Instance methods, class methods (`@classmethod`), static methods (`@staticmethod`)

Worked Example 1 — A Circle showing all three

class Circle:
    PI = 3.14159                 # class attribute (shared)
 
    def __init__(self, r):
        self.r = r               # instance attribute (per-object)
 
    def area(self):                          # INSTANCE method
        return Circle.PI * self.r ** 2       # needs self.r
 
    @classmethod
    def unit(cls):                           # CLASS method (factory)
        return cls(1)                        # builds an instance via cls
 
    @staticmethod
    def is_valid(r):                         # STATIC method (pure helper)
        return r > 0
c = Circle(2)
c.area()          # 12.566  -> needs the object's r
Circle.unit()     # Circle(r=1) -> no object needed, uses cls
Circle.is_valid(-1)  # False -> no self, no cls
  • Why area is an instance method? It depends on self.r, which differs per object.
  • Why unit is a classmethod (not static)? It returns cls(1). Using cls means a subclass class Ball(Circle) would get Ball.unit() → a Ball, not a Circle. This is the killer reason to prefer @classmethod for factories.
  • Why is_valid is static? It uses neither object data nor class data — it's just related logic.

Worked Example 2 — Why cls beats hard-coding the class name

class Animal:
    species = "generic"
 
    @classmethod
    def describe(cls):
        return f"I am a {cls.species}"   # cls, NOT Animal
 
class Dog(Animal):
    species = "dog"
 
Animal.describe()  # "I am a generic"
Dog.describe()     # "I am a dog"   <-- cls is Dog here
  • Why this step (cls.species)? Because cls is whatever class the call came through. Calling via Dog makes cls = Dog, so it correctly reads Dog.species. Had we written Animal.species, subclasses would be ignored.

Worked Example 3 — Alternate-constructor pattern (the 80/20 use of classmethods)

class Date:
    def __init__(self, y, m, d):
        self.y, self.m, self.d = y, m, d
 
    @classmethod
    def from_string(cls, s):        # "2024-05-01"
        y, m, d = map(int, s.split("-"))
        return cls(y, m, d)         # Why cls? subclass-friendly factory
  • Why this step? A class can have only one __init__, but many ways to build an object. Classmethods give you multiple named constructors.


Common Mistakes (Steel-manned)


Recall Feynman: explain to a 12-year-old

Imagine a cookie cutter (the class) and the cookies it makes (the instances).

  • An instance method asks about one specific cookie: "how big are YOU?" — it needs that cookie (self).
  • A class method asks about the cutter itself: "make me a brand-new standard cookie" — it needs the cutter (cls), not a cookie.
  • A static method is a sticky note taped to the cutter: a handy rule like "dough should be thicker than 0 cm" — it doesn't need the cutter or any cookie, it's just kept nearby because it belongs to the topic.

Flashcards

What automatic first argument does an instance method receive?
self, the specific instance the method was called on.
What automatic first argument does a @classmethod receive?
cls, a reference to the class it was called through.
What automatic argument does a @staticmethod receive?
None — it behaves like a plain function namespaced in the class.
Why prefer @classmethod over @staticmethod for factory/alternate constructors?
Because cls respects inheritance: a subclass call builds a subclass instance, not the parent.
Rewrite c.area() as the equivalent explicit function call.
Circle.area(c) — the object becomes the first positional argument.
Why must you write def f(self): and not def f(): for an instance method?
Python passes the object as the first positional arg, so a parameter slot must exist to receive it.
What does obj.method evaluate to under the hood?
type(obj).__dict__['method'].__get__(obj, type(obj)) — a bound method via the descriptor protocol.
In a classmethod, why use cls.x instead of ClassName.x?
So subclasses with overridden x use their own value; cls is the actual calling class.
What happens when you assign self.PI = 3.14 if PI is a class attribute?
It creates a new instance attribute that shadows the class one; the shared class value is unchanged.
Which method type can read/modify both instance AND class state?
Instance method (it has self, and the class is reachable via type(self)).

Connections

Concept Map

is stored as

implements

attribute access triggers

injects self=a

injects cls=type a

injects nothing

reads/writes

can also access

reads/writes

can build

is just

this object

the class

none

Method inside class

Plain function in __dict__

__get__ descriptor protocol

Binding decision

Instance method

Class method @classmethod

Static method @staticmethod

Instance state

Class state

New instances

Namespaced utility logic

Context needed

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, ek class ke andar jo functions hote hain unhe methods kehte hain. In teeno methods me actual difference sirf ek hai: pehla argument automatically kya pass hota hai. Instance method me self jaata hai — yaani jis particular object pe call kiya, wahi object. Class method (@classmethod) me cls jaata hai — yaani poori class khud, koi specific object nahi. Static method (@staticmethod) me kuch bhi automatic nahi jaata — ye bas ek normal helper function hai jo logically class ke saath rehta hai.

Andar ki kahani: jab tum c.area() likhte ho, Python uske peeche Circle.area(c) chala raha hota hai — object ko khud first argument bana ke bhej deta hai (descriptor protocol ki wajah se). Isiliye self likhna zaroori hai, warna "takes 0 arguments but 1 given" error aata hai. Yeh implicit nahi hai jaise Java/C++ me this.

Sabse important practical baat: jab tumhe alternate constructor ya factory banana ho (jaise Date.from_string("2024-05-01")), toh @classmethod use karo aur andar cls(...) se object banao. Kyun? Kyunki agar koi subclass banegi, toh cls automatically wahi subclass ban jaayegi, aur sahi type ka object milega. Agar tum @staticmethod me class ka naam hardcode kar doge, toh inheritance toot jaayegi.

Yaad rakhne ka simple rule: self = yeh object, cls = yeh class, static = kuch nahi. Jab decide karna ho — agar object ka data (self.r) chahiye toh instance, agar class ka data ya naya object banana hai toh classmethod, agar dono nahi chahiye toh static. Bas itna 80/20 yaad rakh lo, kaafi hai.

Go deeper — visual, from zero

Test yourself — OOP Fundamentals

Connections