Python Mastery · Chapter 6 · ~22 min · Intermediate
Classes & Objects Visualised
Move from writing loose functions to designing full software systems. See how classes act as factories stamping out independent objects, why self binds instance state, and how magic dunder methods make your objects feel like native Python types.
- See how classes stamp out independent objects
- Demystify what 'self' actually does behind the scenes
- Harness dunder methods (__str__, __len__, __eq__)
- Avoid the infamous mutable class attribute trap
1The Blueprint & Cookie Cutter mental model
Think of a Class as an architectural blueprint or a cookie cutter. The blueprint describes what an account has (data) and what it can do (methods), but the blueprint itself does not hold money. An Instance (Object) is the actual house built from that blueprint.
Class (The Blueprint)
Written once with class BankAccount:. Specifies attributes and methods.
Instance (The Live Object)
Created as many times as you need: asha = BankAccount("Asha", 500).
asha.deposit(100), Python secretly translates it to BankAccount.deposit(asha, 100). That's all self is: the specific instance passed into the function!2Play with the blueprint factory
Instantiate accounts and deposit or withdraw funds. Watch how each instance protects its own private self.balance without interfering with others:
Class Blueprint ➔ Independent Instances
The Blueprint (Class Definition)
class BankAccount:
bank_name = "Apex Bank" # Class variable (shared)
def __init__(self, owner, balance=0):
self.owner = owner # Instance variable (unique)
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
def withdraw(self, amount):
if self.balance >= amount:
self.balance -= amount
return True
return FalseLive Instances in Memory (2 active objects)
self.owner = "Asha"
TARGET INSTANCE ("self")self.owner = "Ravi"
Click an action below to call an instance method.
What does this code do?
class Dog:
def __init__(self, name):
self.name = name
d1 = Dog('Buddy')
d2 = Dog('Rocky')3Class variables vs Instance variables
Where you define a variable matters tremendously. A variable declared directly inside the class is shared by all instances. A variable attached to self inside __init__ belongs solely to that instance:
Class Level: BankAccount
BankAccount.interest_rate = 5%
Shared across ALL instances unless an instance sets its own copy.
No instance override: falls back to BankAccount.interest_rate.
Reads directly from class variable: updated automatically when BankAccount changes.
What is the output?
class Item:
tax = 0.05
i1 = Item()
i2 = Item()
Item.tax = 0.10
print(i1.tax)4Dunder (Magic) methods
Methods that start and end with double underscores (dunder) let your custom objects integrate seamlessly with Python's built-in operators and functions:
print(item) or str(item)
Human-friendly readable summary (for customers / UI).
Order #101 for Asha ($150.00)
Implementation in your class
def __str__(self):
return f"Order #{self.id} for {self.customer} ($ {self.total})"What is the difference between __str__ and __repr__?
5The 4 classic OOP traps
Trap 1 — The Mutable Class Attribute Bug. If you define a list or dict directly in the class body, every single instance shares the exact same collection!
# ❌ THE TRAP: items is a CLASS variable!
class Store:
items = [] # Shared across EVERY Store instance!
delhi = Store()
mumbai = Store()Trap 2 — Forgetting 'self' in method definitions. If you write def bark(): without self, calling dog.bark() crashes with TypeError: bark() takes 0 positional arguments but 1 was given.
Trap 3 — Missing parentheses on method calls. Writing print(account.get_balance) prints the method reference rather than calling it. Always add parentheses: account.get_balance().
Trap 4 — Fragile deep inheritance. Don't force everything into a subclass hierarchy. Look at composition below:
Composition: 'Has-A'
A Car HAS AN Engine. An Order HAS Items. Instead of inheriting from everything, build objects out of smaller parts.
Software Engineering Principle
"Favor Composition over Inheritance": Deep inheritance trees (A extends B extends C extends D) become brittle and hard to refactor. Combining clean components gives you maximum flexibility with zero surprises.
6Modern Python: Clean Dataclasses
Writing def __init__(self, a, b, c): self.a = a ... repeatedly is tedious. Python's built-in @dataclass decorator generates __init__, __repr__, and __eq__ for you automatically:
from dataclasses import dataclass
@dataclass
class Customer:
id: int
name: str
email: str
active: bool = True
c = Customer(1, "Asha", "asha@example.com")
print(c) # Customer(id=1, name='Asha', email='asha@example.com', active=True)Explore and run full classes, dataclasses, and method pipelines in the Python Playground.
7Challenge: Object-Oriented Architect
Demonstrate your understanding of classes, self, dunder methods, and attribute scopes. Get all 5 correct to finish Chapter 6!
Classes & Objects Visualised · code lab
Edit the example, run it with Python, or step through the lines.
1class Counter:2 def __init__(self, start=0):3 self.value = start4 def add(self, amount):5 self.value += amount67first = Counter(2)8first.add(3)9print(first.value)
Names
Objects in memory
Python output
Run the code to see its output.
1. Why is 'self' passed as the first parameter to instance methods in Python?
2. Which dunder method is invoked when you pass an object into print(obj)?
3. Where should mutable collections (like lists or dicts) be initialized to avoid shared state bugs?
4. How does a child class call its parent's constructor method?
5. What does Python output when you type 'account.deposit' (without parentheses)?
🎯 0 / 5 correct
Run the full e-commerce OOP pipeline with @dataclass and custom dunders in the Python Playground (code preloaded).
8Recap
- A class is the blueprint; an instance (object) is the live entity.
- self is how an instance refers to its own private state inside methods.
- Initialize lists and dicts inside
__init__onselfto avoid the shared class attribute trap. - Use dunder methods (
__str__,__len__,__eq__) to make your classes feel native. - Prefer Composition ("has-a") over deep Inheritance ("is-a") for resilient design.
- Use @dataclass to eliminate boilerplate when building data containers.
Finish the challenge above to complete the chapter, or