2.1.5OOP Fundamentals

`self` — what it is and how Python passes it

1,726 words8 min readdifficulty · medium

WHAT is self?

WHY does it exist? A class is a blueprint shared by many objects. One def deposit(...) body must work for alice's account and bob's account. The method needs to know which account's balance to change. self is that "which one" — it carries the data of the current object.

class Account:
    def __init__(self, owner, balance):
        self.owner = owner        # store ON this instance
        self.balance = balance
 
    def deposit(self, amount):
        self.balance += amount    # change THIS instance's balance
        return self.balance

HOW Python passes self (the core mechanism)

These two lines are equivalent:

alice.deposit(50)        Account.deposit(alice, 50)\texttt{alice.deposit(50)} \;\;\equiv\;\; \texttt{Account.deposit(alice, 50)}

So self ← alice and amount ← 50. That's the whole trick. No hidden global, no magic — just argument binding.

Why does alice.deposit "know" about alice?

Because of the descriptor protocol. A function stored in a class is a descriptor: when you access it via an instance (alice.deposit), Python returns a bound method — a small object that remembers the instance and the function:

print(alice.deposit)            # <bound method Account.deposit of <Account...>>
print(alice.deposit.__self__)   # the alice instance itself
print(alice.deposit.__func__)   # the underlying plain function
Figure — `self` — what it is and how Python passes it

Derivation from scratch: rebuild self with no classes

To prove self is just an argument, let's simulate methods using plain functions and dicts (an object is "data + functions that take that data").

# An "object" is just a dict of data.
def make_account(owner, balance):
    return {"owner": owner, "balance": balance}
 
# A "method" is a plain function whose first param is the object.
def deposit(self, amount):       # <-- self chosen by us, nothing special
    self["balance"] += amount
    return self["balance"]
 
alice = make_account("Alice", 100)
print(deposit(alice, 50))        # 150  -- WE pass the object manually

Why this step? It shows the "automatic" self in real classes is exactly this manual passing — Python just does the deposit(alice, ...) part for you via the dot.


Worked examples


Common mistakes (steel-manned)


Active recall

Recall Q: What does

obj.m(x) translate to under the hood? Class.m(obj, x) — the instance becomes the first positional argument.

Recall Q: Is

self a keyword? No — it's a naming convention. Any valid name works, but always use self.

Recall Q: Why must you write

self.balance and not balance? Bare balance is a local variable. Instance attributes live on the object, accessed via self..

Recall Q: What is a bound method?

A package of function + instance; calling it auto-injects that instance as the first arg.

Recall Feynman: explain

self to a 12-year-old Imagine a recipe (the class) pinned in a kitchen. Many cooks (objects) use the same recipe. When a cook follows step "add sugar to your bowl", self is "your bowl" — it tells the recipe which cook's bowl to use. The recipe doesn't change; only whose bowl does. When you tell a cook "make cake!", you automatically hand them their own bowl — that handing-over is Python passing self.



Flashcards

What is self in a Python method?
A reference to the specific instance the method was called on, passed as the first argument.
alice.deposit(50) is equivalent to what explicit call?
Account.deposit(alice, 50).
Is self a Python keyword?
No, it's a convention; any name works but self is standard.
Why does forgetting self in def greet(): raise "takes 0 positional arguments but 1 was given"?
Python still passes the instance automatically, so 1 arg arrives at a 0-arg function.
What is a bound method's __self__?
The instance the method is bound to.
Why use self.balance instead of balance inside a method?
Bare balance is a local variable; self.balance accesses the instance attribute.
What does returning self enable?
Method chaining / fluent interfaces (e.g. obj.a().b()).
How does alice.deposit know about alice?
Via the descriptor protocol, which returns a bound method carrying the instance.

Connections

Concept Map

shared by many

method needs which one

is a

references

sugar for

binds

triggers

returns

packages

injects as first arg

carries data of

holds

Class blueprint

Instances alice bob

self

Name convention not keyword

Specific instance

instance.method args

Class.method instance, args

Descriptor protocol

Bound method

function + instance

balance owner

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, self koi jaadu nahi hai — ye bas object khud hai jo method ko pehle argument ke roop mein milta hai. Jab tum likhte ho alice.deposit(50), toh Python andar-andar use Account.deposit(alice, 50) bana deta hai. Matlab dot ke left wala object automatically pehle slot mein chala jaata hai. Isi liye method ke andar self ka matlab hota hai "jis object par call hua usi ka data". Isi se alice aur bob ke accounts alag-alag rehte hain, chahe deposit ka code ek hi ho.

Sabse important baat: self ek keyword nahi, sirf ek convention hai. Tum chaaho toh this ya banana bhi likh sakte ho, par sab log self use karte hain taaki code readable rahe. Aur instance ka data access karne ke liye hamesha self.balance likho — sirf balance likhoge toh Python use local variable samjhega aur NameError ya galat result aayega.

Do common galtiyan yaad rakho. Pehli: method define karte waqt self likhna bhool jaana — phir C().greet() pe error aata hai "takes 0 arguments but 1 was given", kyunki Python instance toh phir bhi bhej raha hai. Doosri: alice.deposit(alice, 50) likhna — tum self do baar de rahe ho, dot already de chuka hai. Bas alice.deposit(50) kaafi hai.

Ek line mein mantra: "Dot fills the slot." Jis object ke aage dot lagaya, wahi self ban gaya. Yahi pure OOP ka foundation hai — ek hi blueprint, alag-alag objects, har ek apna data self ke through sambhaale hue.

Go deeper — visual, from zero

Test yourself — OOP Fundamentals

Connections