← Back to list

What self Actually Is in Python :Once You See It, You Can't Unsee It

For the longest time, self was just... a ritual. You write def method(self):, you write self.something, you don't ask questions, you move…

Khushi shukla · 2026-08-06 15:10 · 0 claps · 4.4 min read
#python #oops-concepts #python-class #basic-python #programming-concepts
Open on Medium ↗
Wiki topics: 💻 · Programming

What self Actually Is in Python :Once You See It, You Can't Unsee It

For the longest time, self was just... a ritual. You write def method(self):, you write self.something, you don't ask questions, you move on. I knew where to put it. I didn't know why it was there.

Then it clicked, and I want to write down exactly what clicked — because I think the reason it’s confusing is that most explanations start with the wrong question.

The wrong question: “What is self?”

Most tutorials answer this first, and it’s the wrong entry point. self is not a keyword. It's not special syntax. It's not reserved by Python at all — you could name it banana and your code would still run (please don't). It's just a parameter, like any other, that happens to follow a very strong convention.

The right question is: why does every method need a way to refer back to “the thing it was called on”?

The right question: how does a method know which object it belongs to?

Here’s the core idea, and it’s the part I was missing:

A class is a blueprint. It defines behavior (methods) that’s meant to be shared by every object created from it. But when you actually call a method, you’re not calling it in the abstract — you’re calling it on a specific object.

dog1 = Dog("Tommy")
dog2 = Dog("Rex")
dog1.bark()

When you write dog1.bark(), Python needs to know: bark according to whom? Tommy's name, or Rex's? The method body is shared code — one single bark function lives on the class, not one copy per dog. So the method needs some way to know it's currently operating on dog1, not dog2.

That’s the whole job of self. Python takes dog1.bark() and, under the hood, rewrites it as:

Dog.bark(dog1)

The instance you called the method on gets silently passed in as the first argument. You just choose to name that first parameter self in your function definition, because that's the convention every Python programmer follows.

You can actually see this yourself:

class Dog:
    def bark(self):
        print("id inside method:", id(self))
dog1 = Dog("Tommy")
print("id outside method:", id(dog1))
dog1.bark()

Both id() calls print the exact same number. That's not a coincidence and it's not Python "syncing" two separate objects — self and dog1 are pointing at the same memory location the whole time. self never held a copy of dog1; it is dog1, just visible under a different, local name inside the method's scope. Same object, same address, two labels.

Why does Python make you write self at all?

Here’s the part that isn’t strictly mechanics — it’s a design choice, and it’s worth understanding why Python made it, because it’s genuinely unusual.

In languages like Java, C++, or JavaScript, the “current object” is available inside a method automatically, through a keyword like this. You never declare it as a parameter — it's just there, implicitly, the moment you're inside a method.

Python could have done the same thing. It didn’t. And the reason comes straight from Python’s design philosophy: explicit is better than implicit. Guido van Rossum (Python’s creator) has said directly that he wanted it to be visually obvious, right in the function signature, that a method receives the instance as data — because under the hood, that’s genuinely all it is. A method is not some special category of function that mysteriously gains access to “its” object. It’s an ordinary function that happens to take the instance as its first argument, and Python would rather show you that plainly than hide it behind a keyword that makes it feel more magical than it is.

That’s the “feel factor” you’re describing, and I’d argue it runs the other way: self isn't there because Python assumes you need convincing that a method belongs to an object. It's there because Python refuses to pretend that connection is automatic. Every other language quietly tells you "trust me, the object is in there somewhere." Python hands it to you as the first parameter and says "here it is, this is literally an argument, nothing is hidden from you." Once you've seen id(self) == id(dog1) with your own eyes, that stops being a philosophy you take on faith — it's just what's true.

Why this explains what you noticed

You noticed something sharp: methods can’t just reach out and call each other freely, or touch each other’s variables, unless they go through self. That's exactly right, and here's the reasoning underneath it:

Each method is its own little function with its own local scope. greet() and another_method() on the same class don't share variables just because they live in the same class body — no more than two unrelated functions in a file share variables. The only thing connecting them is that they both get handed the same self when called on the same object.

So when you write:

class Robot:
    def charge(self):
        self.battery = 100
    def status(self):
        self.charge()
        print(f"Battery at {self.battery}%")

self.charge() inside status isn't some special cross-method access rule. It's just: "call charge, and pass in the same instance I was called with." Because self is the object, self.battery set inside charge and self.battery read inside status are reading and writing the same object's attribute dictionary. Nothing mystical — it's the same mechanism as dog1.bark(), just triggered from inside another method instead of from outside the class.

A mental model that actually holds up

Forget “self refers to the instance” as a floating fact to memorize. Instead:

  • A method is just a function sitting on a class.
  • Calling it via obj.method() is syntactic sugar for Class.method(obj).
  • self is nothing but the name given to that first incoming argument.
  • Because every method receives the same self when called on the same object, methods can cooperate — via self.other_method() — and share state — via self.attr.

Once you see it as “just an argument that gets passed automatically,” a bunch of other Python behavior stops being mysterious too:

  • Why you can’t call obj.method() and skip self in the definition — you're not skipping anything; Python is still passing obj in, there's just no parameter left to catch it, so you get a TypeError about missing arguments.
  • Why @staticmethod exists — it's a method that explicitly opts out of receiving the instance at all, because it doesn't need one.
  • Why @classmethod uses cls instead of self — same mechanism, except what gets passed in automatically is the class, not an instance.

The takeaway

self isn't Python being weird for weirdness's sake. It's the visible seam where Python admits something honest: methods are just functions, classes just group them, and the only reason obj.method() "knows" which object it's working on is that Python quietly hands that object to the method every single time. Once that seam is visible, self.anything() stops feeling like a rule you're obeying and starts feeling like the obvious consequence of how the call happened in the first place.


메타데이터
post_id
d9fb53f3b0f6
slug
the-day-self-finally-made-sensewhat-self-actually-is-in-python-once-you-see-it-you-cant-unsee-it-d9fb53f3b0f6
url
https://medium.com/@khushi28132004/the-day-self-finally-made-sensewhat-self-actually-is-in-python-once-you-see-it-you-cant-unsee-it-d9fb53f3b0f6
canonical_url
https://medium.com/@khushi28132004/the-day-self-finally-made-sensewhat-self-actually-is-in-python-once-you-see-it-you-cant-unsee-it-d9fb53f3b0f6
author_url
https://medium.com/@khushi28132004
status
ok
fetched_at
2026-09-01 12:40:59