Classes vs Objects in Python: What's the Difference?
The Problem
When I started learning Python, I could understand functions, loops, and boolean statements. But classes felt weird.
I saw code like this:
class Dog: def __init__(self, name, breed): self.name = name self.breed = breed
def bark(self): return f"{self.name} says Woof!"And I had so many questions:
- What is
selfand why does it appear everywhere? - Why write
class Dog:but then callDog()like a function? - What’s the difference between
Dog(the class) anddog1(an object)? - Why do I need
__init__with those weird underscores?
I tried memorizing the syntax, but it didn’t make sense. The code worked, but I didn’t understand WHY it worked.
The Core Issue
The problem is that Python classes introduce object-oriented programming (OOP), which is a different way of thinking about code.
When you write a function, you write code that runs. When you write a class, you’re writing a template that doesn’t do anything by itself.
Let me show you what I mean.
Class = Blueprint
A class is a blueprint. It defines what attributes and methods something will have, but it doesn’t contain any actual data.
Think about a cookie cutter. The cutter defines the shape of the cookies, but you can’t eat the cutter. You eat the cookies that you make with it.
In Python:
class Dog: def __init__(self, name, breed): # This defines WHAT a dog has self.name = name self.breed = breed
def bark(self): # This defines WHAT a dog can do return f"{self.name} says Woof!"This Dog class is like the cookie cutter. It says “a dog has a name and breed” and “a dog can bark.” But there’s no actual dog here. No real data. Just a template.
Object = Instance
An object is when you actually use that blueprint to create something real.
dog1 = Dog("Buddy", "Golden Retriever")dog2 = Dog("Max", "German Shepherd")Now we have two actual dogs. Each one is a separate object with its own name and breed.
When I run this:
print(dog1.bark())print(dog2.bark())print(dog1.name)print(dog2.name)I get:
Buddy says Woof!Max says Woof!BuddyMaxYou can see that each object has its own data. dog1 has name “Buddy” and dog2 has name “Max”. They’re both created from the same Dog blueprint, but they’re separate instances.
The Self Mystery
The self parameter confused me the most. Why do I need it?
self is Python’s way of saying “this specific object.” When you call dog1.bark(), Python passes dog1 as self. When you call dog2.bark(), Python passes dog2 as self.
Here’s a clearer example:
class Person: def __init__(self, name): self.name = name # Store name in THIS object
def greet(self): return f"Hello, I'm {self.name}" # Use THIS object's name
person1 = Person("Alice")person2 = Person("Bob")
print(person1.greet()) # Output: "Hello, I'm Alice"print(person2.greet()) # Output: "Hello, I'm Bob"When person1.greet() runs, self is person1. When person2.greet() runs, self is person2. That’s how each object knows which data to use.
Class Variables vs Instance Variables
I made a mistake early on. I tried using class variables for data that should be unique to each object.
Here’s what went wrong:
class DogWrong: legs = 4 # Class variable - shared by ALL dogs
dog1 = DogWrong()dog2 = DogWrong()
dog1.legs = 3 # This creates an instance variable on dog1print(dog1.legs) # Output: 3print(dog2.legs) # Output: 4 - dog2 still sees the class variable!I thought changing dog1.legs would affect all dogs, but it didn’t. That’s because dog1.legs = 3 created a new instance variable on just that one object.
Here’s the right way:
class DogRight: def __init__(self, legs=4): self.legs = legs # Instance variable - unique per object
dog1 = DogRight(3) # Dog with 3 legsdog2 = DogRight() # Dog with 4 legs
print(dog1.legs) # Output: 3print(dog2.legs) # Output: 4Now each dog has its own legs value stored in the object itself, not shared across all objects.
Real-World Analogy
Think about a house blueprint:
- Blueprint (Class): Shows 3 bedrooms, 2 bathrooms, kitchen layout. You can’t live in the blueprint.
- Houses (Objects): You build 100 houses from that blueprint. Each house is at a different address, has different owners, different furniture. But they all follow the same layout.
In code:
class House: def __init__(self, address, owner): self.address = address # Unique to each house self.owner = owner # Unique to each house self.rooms = 3 # All have 3 rooms (from blueprint) self.bathrooms = 2 # All have 2 bathrooms (from blueprint)
house1 = House("123 Main St", "Alice")house2 = House("456 Oak Ave", "Bob")
print(house1.address) # Output: "123 Main St"print(house2.address) # Output: "456 Oak Ave"The blueprint defines the structure. Each house (object) has its own address and owner, but they all follow the same floor plan.
Why This Matters
Understanding the class vs object distinction changed how I write code:
- Classes organize code: I put related data and behavior together in one place
- Objects manage state: Each object keeps track of its own data
- Reusability: I write the class once, create as many objects as I need
- Real-world modeling: I can map code to real things like User, Product, Order
Here’s a practical example:
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.balance = balance
def deposit(self, amount): self.balance += amount return f"Deposited ${amount}. New balance: ${self.balance}"
def withdraw(self, amount): if amount > self.balance: return "Insufficient funds" self.balance -= amount return f"Withdrew ${amount}. New balance: ${self.balance}"
account1 = BankAccount("Alice", 1000)account2 = BankAccount("Bob", 500)
print(account1.deposit(200))print(account2.withdraw(100))print(account1.balance)print(account2.balance)Output:
Deposited $200. New balance: $1200Withdrew $100. New balance: $4001200400Each account object maintains its own balance. When I deposit to account1, it doesn’t affect account2. They’re independent objects created from the same blueprint.
The init Method
The __init__ method runs automatically when you create an object. It sets up the initial state of the object.
class Student: def __init__(self, name, grade): # This runs when you create a Student object self.name = name self.grade = grade self.assignments = [] # Empty list for each student
def add_assignment(self, score): self.assignments.append(score)
student1 = Student("Alice", 10)student2 = Student("Bob", 11)
# Each student has their own assignments liststudent1.add_assignment(95)student2.add_assignment(87)
print(student1.assignments) # Output: [95]print(student2.assignments) # Output: [87]If I had made assignments a class variable, all students would share the same list. That would be a bug. By putting it in __init__, each student gets their own list.
Summary
In this post, I explained the difference between classes and objects in Python. The key point is:
- Class: A blueprint or template that defines structure and behavior
- Object: A specific instance created from a class with actual data
The cookie cutter analogy helped me: the cutter (class) defines the shape, but the cookies (objects) are what you actually eat. Each cookie is separate, but they all come from the same cutter.
Once I understood this distinction, the self parameter made sense (it refers to “this specific object”), and I could see why __init__ needs to initialize instance variables for each object separately.
The best way to learn this is to practice. Try creating simple classes like Car, Book, or ShoppingCart and experiment with creating multiple objects to see how they maintain separate data.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 r/learnpython discussion on classes and objects
- 👨💻 Python official documentation on classes
- 👨💻 Real Python's Object-Oriented Programming (OOP) in Python 3
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments