Visual walkthrough — Instance methods, class methods (`@classmethod`), static methods (`@staticmethod`)
This page assumes nothing. We build the whole chain from the very first idea: a method is just a function stored in a box.
Step 1 — A class is a box that holds functions
WHAT. We put a function named f into the class box A.
WHY. Because everything that follows is just looking things up in that box. If you picture the box clearly now, the rest is bookkeeping.
PICTURE. Look at the figure: the outer rounded box is the class A. Inside it sits A.__dict__, a table. The row "f" → <function f> is highlighted — that is our method, sitting there as a plain function, exactly like a function you'd write outside any class.

Recall Check yourself
Where does the function f physically live after class A: def f(self): ...? ::: In A.__dict__ under the key "f", as an ordinary function.
Step 2 — A function is a descriptor: it knows how to "be fetched"
Every Python function is secretly a descriptor. So a function has a hidden __get__ waiting to run.
WHAT. We reveal that the plain function from Step 1 carries a hidden method, __get__(instance, owner).
WHY. This is the entire secret. self does not appear by magic — it appears because __get__ runs at the exact moment you access the method and stuffs the object in.
PICTURE. The function box now shows a little side-lever labelled __get__. Two empty slots hang off it: one waiting for instance, one for owner (the class). Right now both are empty because nobody has fetched f yet.

Related idea if you want to go deeper later: Descriptors and the __get__ protocol.
Step 3 — Accessing a.f triggers the fetch-lever
Now we make an object a from the class A (an ==instance== = one cookie from the cutter) and we access the attribute a.f.
WHAT. Writing a.f is not a simple table peek. Python rewrites it into a call to the descriptor's __get__.
WHY. Because f is a descriptor (Step 2), Python is obligated to run its __get__ instead of returning the raw function. This is the moment the object a gets captured.
PICTURE. Watch the arrow flow: a.f → Python looks in type(a).__dict__ → finds the descriptor → pulls its __get__ lever → plugs a into the instance slot and type(a) into the owner slot. The two empty slots from Step 2 are now filled (colour them in!).

Step 4 — __get__ returns a bound method that remembers a
WHAT. The function's __get__ does not return the plain function. It returns a brand-new object: a ==bound method== — a wrapper that has glued a onto the function.
WHY. So that later, when you actually call it with (), the object is already attached and gets handed in first. The gluing happens at fetch-time; the injection happens at call-time.
PICTURE. The figure shows two layers. On the left, the raw function f(self) with an empty first slot. __get__ produces the right-hand object: the same function with the first slot pre-filled by a (drawn as a clip fastening a to the front). That pre-filled wrapper is the bound method.

Step 5 — @classmethod: same lever, different thing injected
Now we change one thing: wrap the function in ==@classmethod== (a decorator — a wrapper that swaps the descriptor).
WHAT. @classmethod replaces the plain-function descriptor with a different descriptor whose __get__ injects the class into the first slot instead of the object.
WHY. Sometimes we want context about the cutter, not one cookie — to read class-wide data or build fresh instances. The class method needs the class, so its __get__ hands over type(a), conventionally named ==cls==.
PICTURE. Same lever machinery as Step 3, but the arrow now routes the class box into the first slot (colour it lavender, matching the class), while the object a is left aside, unused.

This is exactly why a subclass factory works — see Instance vs Class attributes and Factory design pattern.
Step 6 — @staticmethod: the lever injects nothing
WHAT. ==@staticmethod== swaps in yet another descriptor whose __get__ simply hands back the raw function — no first argument injected at all.
WHY. Some helpers logically belong to the class but need neither a cookie nor the cutter — pure utility logic. So its __get__ refuses to add anything.
PICTURE. The lever runs, but the arrow that used to carry a (Step 4) or the class (Step 5) is now cut (drawn as a scissored, greyed line). The function comes out with its first slot still empty — a plain function, just kept in the class drawer.

Step 7 — Edge case: fetching through the class, or with no instance
We must not leave a scenario unshown. What happens for A.f (no object at all)?
WHAT. When you access a plain function through the class, type(a) doesn't exist because there is no a. Python calls f.__get__(None, A) — instance is None.
WHY. In modern Python (3.x), __get__ with instance=None returns the plain function itself, unbound. So A.f gives you back the function, and you must pass the object yourself.
PICTURE. Three parallel tracks. Track 1 A.f → instance=None → raw function comes out (you supply self manually: A.f(a)). Track 2 A.g (classmethod) → cls is still injected as A, so A.g() works with no object. Track 3 A.h (staticmethod) → nothing injected either way, A.h() works.

The one-picture summary
Everything above collapses into a single diagram: one attribute access x.method, one call to __get__, and a three-way switch deciding what fills the first argument slot.

- The coral path (plain function) injects the object → born as
self. - The lavender path (
@classmethod) injects the class → born ascls. - The mint path (
@staticmethod) injects nothing → a plain namespaced helper.
Recall Feynman: the whole walkthrough in plain words
A class is a drawer. When you write a method, you drop a plain function into that drawer (Step 1). Every function secretly has a little fetch-lever called __get__ (Step 2). The instant you write a.f, Python doesn't just grab the function — it pulls the lever, and the lever grabs your object a and clips it onto the front of the function (Steps 3–4). So a.f() is really A.f(a) — the object becomes self. If you wrap the function in @classmethod, the lever is swapped for one that clips on the class instead (that's cls, Step 5). Wrap it in @staticmethod and the lever clips on nothing — it's just a function kept in the drawer for tidiness (Step 6). And if you fetch through the class with no object around, the class and static versions still work (they don't need a cookie), while a plain instance method hands you the bare function and asks you to supply the object yourself (Step 7). One lever, three clip-on rules. That is the entire "magic."
Where to go next
- Back to the parent: 2.1.04 Instance methods, class methods (`@classmethod`), static methods (`@staticmethod`) (Hinglish)
- The general machinery: Descriptors and the __get__ protocol
- How the lookup box is searched across parents: Inheritance and method resolution order (MRO)
- The data these methods touch: Instance vs Class attributes
- The wrappers themselves: Decorators
- The classic classmethod use: Factory design pattern
- Foundations: OOP Fundamentals - Classes and Objects