Intuition The one core idea
A method is just a function that lives inside a class, and Python automatically slips one extra argument into the front when you call it. The only thing that separates instance, class, and static methods is what that hidden first argument is : the object, the class, or nothing at all.
Before you can understand why self, cls, and "nothing" appear, you must be fluent in the small pile of words and symbols the parent note leans on without stopping to explain. This page builds every one of them from the ground up, in the order they depend on each other.
A class is a blueprint that describes what kind of thing an object is and what it can do. It is written with the keyword class.
Picture a cookie cutter . The cutter is not a cookie — you can't eat it — but every cookie you stamp out has the shape the cutter defines.
Look at the figure: the violet shape on the left is the class (the cutter). The three orange shapes on the right are objects stamped from it. The class holds the plan ; the objects are the real things .
Intuition Why the topic needs it
All three method kinds (instance, class, static) live inside a class. Without the idea of a blueprint, the words "class method" and "instance method" have nothing to attach to.
Related vault note: OOP Fundamentals - Classes and Objects .
Definition Object (instance)
An object is one concrete thing built from a class. Because it is a single instance of the blueprint, we also call it an instance . The two words mean the same thing.
In the figure above, each orange cookie is one instance of the violet cutter. If the cutter is Circle, then a circle of radius 2 is an instance of Circle.
Intuition Why the topic needs it
An instance method is a method that needs a particular instance to do its job — "how big are you ?" The word "instance" is literally in the name.
Definition The dot operator
a.b means: ==look inside a and fetch the thing named b==. It is how you reach data or methods that belong to an object or class.
Read c.area out loud as "c's area ". The dot is a little arrow that walks into the thing on its left and picks out the name on its right.
Intuition Why the topic needs it
Every derivation in the parent — a.f, c.area(), Circle.PI — is built on the dot. And the whole secret of how self appears is hidden inside what the dot does. You cannot understand self without first understanding that a.f is not a simple lookup — it triggers machinery. That machinery is the descriptor protocol (section 8).
An attribute is any name you can reach with the dot. c.r (data) and c.area (a method) are both attributes of c.
There are two homes an attribute can live in:
Definition Instance attribute vs class attribute
An instance attribute lives on one object . Written self.r = .... Each cookie has its own.
A class attribute lives on the blueprint and is shared by every object. Written directly in the class body, e.g. PI = 3.14159.
In the figure, the shared value PI (magenta) sits on the class and every object points up to it. But each object carries its own r (orange) locally.
Deeper dive available at Instance vs Class attributes .
A function is a reusable block of code that takes inputs (called parameters / arguments ) and gives back a result. Written with def name(inputs):.
The figure shows a plain function floating free (left) versus the same function placed inside a class box (right). The code is identical; the only change is its address . That address is what lets Python attach the automatic first argument.
Intuition Why the topic needs it
The parent's central claim — "a method is just a function living inside a class" — is this distinction. Once you see a method as an ordinary function with a home, the mystery of self dissolves into "who fills the first parameter slot?"
Definition Parameter vs argument
A parameter is a named slot in a function's definition: the x in def f(x):.
An argument is the actual value you hand in when calling: the 5 in f(5).
The word positional matters here:
Definition Positional argument
A positional argument is filled by its position in line — the first value goes to the first slot, the second to the second, and so on.
Intuition Why the topic needs it
Python fills the first positional slot of a method automatically. That auto-filled first slot is the entire mechanism:
instance method → first slot gets the object (we name it self)
class method → first slot gets the class (we name it cls)
static method → first slot is left alone (nothing injected)
self and cls are not keywords — they are just conventional names for that first slot. You could rename them and it would still work; you just shouldn't.
A decorator , written @name on the line above a function, wraps that function to change its behaviour without editing its code.
@classmethod ⟹ "change what fills the first slot to the class"
Think of it as a stamp on an envelope that tells the post office (Python) how to handle the letter (the function) — express, standard, or return-to-sender — before it's ever opened.
Intuition Why the topic needs it
@classmethod and @staticmethod are decorators. They are the switches that pick which rule fills the first slot. Everything the parent does with these two symbols is decorator behaviour.
Full treatment: Decorators .
Definition Dunder ("double underscore") names
A name wrapped in double underscores, like __init__, is called a dunder . These are special hooks Python calls automatically at set moments.
__init__ runs once when an object is born , to set up its instance attributes (this is where self.r = r lives).
__dict__ is the actual storage drawer — a dictionary mapping attribute names to their values. Circle.__dict__['f'] is literally where the function f is kept.
Intuition Why the topic needs it
The parent's derivation writes type(a).__dict__['f']. That means: "open the class's storage drawer and pull out the function named f." You need to know __dict__ is just a drawer to follow that line.
This is the deepest prerequisite, so we build it slowly.
A descriptor is any object that defines a method called __get__. When you fetch a descriptor through the dot, Python doesn't just hand it back raw — it calls its __get__ and hands you whatever that returns .
Here is the key secret the parent uses:
Walk the figure left to right: you type a.f → the dot triggers __get__ → out pops a bound method that already holds a → you add () → Python calls A.f(a), so self = a. No magic keyword — just a descriptor doing its job.
Intuition Why the topic needs it
The three method kinds are the same mechanism with three different __get__ rules :
plain function's __get__ injects the object → self
classmethod's __get__ injects the class → cls
staticmethod's __get__ injects nothing
Once you own "descriptor = a thing whose __get__ decides what you receive", all three method types collapse into one idea.
Go deeper at Descriptors and the __get__ protocol .
In the figure of section 8, type(a) is the arrow pointing from a cookie back up to its cutter.
Intuition Why the topic needs it
A @classmethod injects type(a) — the class — into the first slot. That's why cls is the class and not the object, and why a subclass call correctly yields the subclass (the parent's Dog.describe() example).
Definition Subclass / inheritance
A subclass is a new class built on top of an existing one, keeping its behaviour but able to change parts. class Dog(Animal) makes Dog a subclass of Animal.
Intuition Why the topic needs it
The parent's killer reason to prefer @classmethod for factories is inheritance: because cls is filled with type(a), calling a factory through Dog produces a Dog, not an Animal. Without the idea of subclasses, that advantage is invisible.
Related: Inheritance and method resolution order (MRO) and Factory design pattern .
instance attribute self.r
parameter and first positional slot
classmethod injects class
staticmethod injects nothing
THE TOPIC: three method kinds
Read the map top-down: blueprints give objects and the dot; the dot plus functions give the descriptor mechanism; the descriptor injects self; decorators swap the injection to give cls or nothing. All arrows converge on the topic.
Cover the right side and answer each. If any stalls you, re-read that section before the main note.
What is the difference between a class and an object? The class is the blueprint (cookie cutter); the object is one concrete thing stamped from it (a cookie).
What does the dot in c.area actually do? It reaches into c and fetches the attribute named area — and for methods it triggers the descriptor's __get__, not a raw lookup.
Where does an instance attribute live vs a class attribute? Instance attribute lives on one object (self.r); class attribute lives on the shared blueprint (PI) and every object sees it.
What is the difference between a function and a method? A method is just a function stored inside a class — same code, different home.
What is a positional argument and why does it matter here? A value filled by its place in line; Python auto-fills the first positional slot of a method with the object, class, or nothing.
What does a decorator like @classmethod do? It wraps the function to change its behaviour — here, to change what fills the first argument slot.
What is __dict__? The storage drawer of a class or object — a dictionary mapping attribute names to their values.
What is a descriptor, and why are functions descriptors? A descriptor is any object with a __get__; functions have one, so a.f returns a bound method carrying a, which becomes self.
Rewrite a.f() as the explicit call it becomes. A.f(a) — the object a is slipped in as the first argument.
Why is cls the class and not the object? Because @classmethod's __get__ injects type(a) (the class), so subclass calls yield subclass instances.
Mnemonic The whole page in one line
Blueprint → object → dot → descriptor → first slot filled. Change what fills the slot and you change which of the three methods you have.