Visual walkthrough — Class vs object — blueprint vs instance
Keep those two pictures in your head: numbered boxes + arrows. Everything below is just boxes and arrows.
Step 1 — The empty machine and the word "name"
WHAT. Before any code runs, we have three regions on our memory wall, and they are empty:
- a place for names you type (
Dog,d1,d2), - a place for class objects (the blueprint, once defined),
- a place for instances (the actual dogs, once built).
WHY. If we don't first agree on where things can live, the arrows later will point nowhere. A picture of the containers must come before a picture of what fills them.
PICTURE. Look at the figure. The three coloured columns are the three regions. Right now every box is blank — this is the machine the instant before we press Run.
Step 2 — Defining the class fills the "blueprint" box (but builds no dog)
WHAT. We run the class definition:
class Dog:
species = "Canis"
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
return f"{self.name} says Woof!"Python creates one object in the class region and points the name Dog at it. Inside that class object it stores:
- the class attribute
species = "Canis", - the code for
__init__andbark.
WHY. This is the parent note's rule "defining a class creates no instance" shown as a picture: the instance region is still empty. A blueprint is itself a real thing in memory — but it is a plan, not a house.
PICTURE. The name Dog (left column) now has an arrow into the class region. That class box holds species and the two method-code blocks. The instance region on the right is still blank.
Step 3 — Dog("Rex", 3) creates a blank instance box
WHAT. We run d1 = Dog("Rex", 3). The first thing Python does is make a brand-new, empty box in the instance region and label it "a Dog". No attributes are written yet.
WHY. __init__ cannot write self.name until there is a self to write into. So Python must build the empty object first, then fill it. Seeing the empty-then-filled order kills the myth that "the class stores the object's data".
PICTURE. A fresh box appears in the instance region. A dashed arrow labelled self points at it — this dashed arrow is the value Python is about to hand to __init__.
Step 4 — __init__ writes data into that one box
WHAT. Now __init__ runs with self pointing at the box from Step 3:
Term by term: self selects the box, .name names a slot inside that box, and = name drops the value "Rex" into that slot. Then the same happens for age = 3.
WHY. This is the exact moment "instance data lives on the instance" becomes true. The write lands inside the instance box, never in the class box.
PICTURE. The instance box now shows name = "Rex" and age = 3. Crucially, notice there is no arrow copying the methods into it — bark is still only in the class box.
Finally, the finished box's arrow is handed back and stored in the name d1.
Step 5 — A second call builds an independent box
WHAT. We run d2 = Dog("Fido", 5). Steps 3–4 repeat, producing a second, separate box in the instance region with its own name = "Fido", age = 5.
WHY. Each call to Dog(...) makes a new box. This is the picture behind the parent's Example 1: two dogs, two independent piles of data.
PICTURE. Two instance boxes side by side. d1's arrow goes to the "Rex" box, d2's arrow to the "Fido" box. Both boxes have a faint arrow back to the same class box — that is where they both find species and bark.
Step 6 — Attribute lookup: how d1.age and d1.species differ
WHAT. When you read d1.age, Python looks inside d1's box first — finds age = 3, done. When you read d1.species, Python looks inside d1's box, finds nothing, and follows the arrow up to the class box, where it finds "Canis".
WHY. This "look here first, then walk up to the class" rule explains both parent examples at once:
d1.age = 4writes intod1's box →d2.ageuntouched (still 5). ✅ Example 1.Dog.species = "Lupus"changes the class box → bothd1.speciesandd2.speciesnow read"Lupus"because they both walk up to it. ✅ Example 2.
PICTURE. Green arrow = the short lookup that stops inside the instance. Orange arrow = the long lookup that walks up to the class box. Same object, two different arrow-paths.
Step 7 — Degenerate & edge cases (the ones that trip people up)
WHAT. Three corner cases, each a distinct arrow-picture:
- Two equal-looking dogs are still two boxes.
a = Dog("Rex",3),b = Dog("Rex",3). Same data, but Step 3 ran twice, so there are two boxes. Hencea is b→False(two arrows, two boxes), whilea == bmay compare values. See Identity vs Equality (is vs ==). - Aliasing — two names, one box.
c = a. No new box is built;cjust copiesa's arrow. Nowc is a→True, andc.age = 9changes whatasees too. One box, two arrows. - Shadowing.
d1.species = "Wolf"creates a new slot insided1's box. Nowd1's short lookup succeeds and never walks up —d1.speciesis"Wolf"butd2.speciesis still"Canis".
WHY. Every one of these confuses beginners because they picture data where the arrows aren't. Drawing the arrows removes the confusion entirely.
PICTURE. Three mini-panels: (1) two boxes, two arrows → is False; (2) one box, two arrows → is True; (3) one box that now carries its own species, breaking the walk-up.
The one-picture summary
WHAT. One frame that folds Steps 1–7 together: names on the left, the single class box (blueprint + shared species + method code) in the middle, and the two independent instance boxes on the right — with the lookup arrows drawn on top.
PICTURE. Follow any name's arrow to its box; follow any missing-attribute lookup up to the class box. That is the entire mental model of class-vs-object.
Recall Feynman: retell the whole walkthrough in plain words
We started with an empty wall of numbered boxes. Writing class Dog: filled one box — the blueprint — with the shared word "Canis" and the recipes __init__ and bark. It built no dog. Then Dog("Rex",3) made a fresh empty box, handed it to __init__ as self, and __init__ wrote name and age into that box. Doing it again made a second box, totally separate. When we ask for d1.age, Python peeks inside d1's own box and finds it. When we ask for d1.species, it doesn't find it there, so it follows the arrow up to the blueprint box and reads it from there — which is why changing species on the class changes it for everyone, but changing d1.age changes it for nobody but d1. Two dogs with the same name are still two boxes (is is False); saying c = a just paints a second arrow at the same box (is is True). Boxes and arrows — that's all objects ever are.
Connections
- 2.1.01 Class vs object — blueprint vs instance (Hinglish) — the parent, in Hinglish.
- Constructors and __init__ — Steps 3–4 in detail.
- self and instance methods — the dashed
selfarrow. - Class vs instance attributes — Step 6's two lookup paths.
- Identity vs Equality (is vs ==) — Step 7 case 1.
- Memory model and references — Step 7 case 2 (aliasing).
- Encapsulation — why we bundle data + behaviour into that one class box.