Object-Oriented Programming (OOP) is a way of structuring a program by bundling related data and the behaviour that works on that data into single units called objects. Instead of a long list of functions and loose variables, you model your program as a collection of interacting objects — which scales far better as projects grow.
Python is a multi-paradigm language, but its OOP support is clean and complete. This guide covers everything end-to-end: classes and objects, __init__ and self, instance vs class attributes, the three kinds of methods, and the four pillars of OOP — encapsulation, inheritance, polymorphism and abstraction — plus the dunder methods that make your objects feel native. Every concept has runnable code with its output.
What Is a Class and an Object?
A class is a blueprint; an object (or instance) is a concrete thing built from that blueprint. A Car class describes what every car has and does; my_car is one actual car.
Syntax:
class ClassName:
# class body: attributes and methods
...
obj = ClassName() # create (instantiate) an object
The __init__ Method and self
__init__ is the constructor — it runs automatically when you create an object and sets up its initial state. self refers to the specific object being created, so self.brand stores data on that object.
class Car:
def __init__(self, brand, speed):
self.brand = brand # instance attribute
self.speed = speed
def describe(self):
return f"{self.brand} going {self.speed} km/h"
car1 = Car("Toyota", 120)
car2 = Car("Tesla", 150)
print(car1.describe())
print(car2.describe())
Output:
Toyota going 120 km/h
Tesla going 150 km/h
Instance vs Class Attributes
An instance attribute belongs to one object (set with self.x). A class attribute is shared by all instances (defined directly in the class body).
class Dog:
species = "Canis familiaris" # class attribute (shared)
def __init__(self, name):
self.name = name # instance attribute (per object)
a = Dog("Rex")
b = Dog("Bella")
print(a.name, b.name) # different
print(a.species, b.species) # same, shared
Output:
Rex Bella
Canis familiaris Canis familiaris
Instance, Class, and Static Methods
Python has three method types: instance methods (take self), class methods (take cls, marked @classmethod), and static methods (take nothing special, marked @staticmethod).
class Pizza:
def __init__(self, size):
self.size = size
def area(self): # instance method
return 3.14159 * (self.size / 2) ** 2
@classmethod
def medium(cls): # class method: alternate constructor
return cls(size=12)
@staticmethod
def is_valid_size(size): # static method: utility, no self/cls
return 6 <= size <= 18
p = Pizza.medium()
print(round(p.area(), 2))
print(Pizza.is_valid_size(20))
Output:
113.1
False
Pillar 1: Encapsulation
Encapsulation means bundling data with the methods that use it, and controlling access to it. By convention, a single underscore _x means βinternalβ, and a double underscore __x triggers name mangling to discourage outside access. Expose controlled access through methods or @property.
class Account:
def __init__(self, balance):
self.__balance = balance # "private" (name-mangled)
def deposit(self, amount):
if amount > 0:
self.__balance += amount
@property
def balance(self): # read-only access
return self.__balance
acc = Account(100)
acc.deposit(50)
print(acc.balance) # 150 via the property
# print(acc.__balance) # AttributeError - cannot touch it directly
Output:
150
Pillar 2: Inheritance
Inheritance lets a child class reuse and extend a parent class. Use super() to call the parent's methods (e.g. its __init__).
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some sound"
class Dog(Animal): # Dog inherits from Animal
def __init__(self, name, breed):
super().__init__(name) # call parent constructor
self.breed = breed
def speak(self): # override
return "Woof!"
d = Dog("Rex", "Labrador")
print(d.name, d.breed)
print(d.speak())
print(isinstance(d, Animal)) # True - a Dog is an Animal
Output:
Rex Labrador
Woof!
True
Pillar 3: Polymorphism
Polymorphism (βmany formsβ) lets different classes respond to the same method call in their own way. Because Python uses duck typing, code only cares that an object has the method, not what class it is.
class Cat:
def speak(self): return "Meow"
class Cow:
def speak(self): return "Moo"
class Duck:
def speak(self): return "Quack"
for animal in (Cat(), Cow(), Duck()):
print(animal.speak()) # same call, different behaviour
Output:
Meow
Moo
Quack
Pillar 4: Abstraction
Abstraction hides implementation details behind a common interface. Python's abc module lets you define abstract base classes with methods that subclasses must implement.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
...
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
s = Square(5)
print(s.area())
# Shape() -> TypeError: Can't instantiate abstract class Shape
Output:
25
Dunder (Magic) Methods
Special methods with double underscores let your objects work with built-in operations like print(), ==, len() and +. The most common are __str__ (readable text), __repr__ (debug text), and __eq__ (equality).
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f"({self.x}, {self.y})"
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
p1, p2 = Point(1, 2), Point(3, 4)
print(p1) # uses __str__
print(p1 + p2) # uses __add__
print(p1 == Point(1, 2)) # uses __eq__
Output:
(1, 2)
(4, 6)
True
Real-World Example: A Bank Account System
Putting it together — encapsulation (private balance), inheritance (a savings account), and a dunder method, in one small but realistic class hierarchy.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount
return self._balance
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
return self._balance
def __str__(self):
return f"{self.owner}'s account: ${self._balance}"
class SavingsAccount(BankAccount):
def __init__(self, owner, balance=0, rate=0.05):
super().__init__(owner, balance)
self.rate = rate
def add_interest(self):
self._balance += self._balance * self.rate
return self._balance
acc = SavingsAccount("Tushar", 1000)
acc.deposit(500)
acc.add_interest()
print(acc)
Output:
Tushar's account: $1575.0
Common Mistakes to Avoid
- Forgetting
selfin method definitions or when accessing attributes. - Using a mutable default like
def __init__(self, items=[])— the list is shared across instances. UseNoneand create inside. - Confusing class and instance attributes — reassigning a mutable class attribute affects every object.
- Skipping
super().__init__()in a child class, leaving the parent's state unset. - Thinking
__privateis truly private — it is only name-mangled, not locked.
Summary Table
| Concept | Keyword / Syntax | Purpose |
|---|---|---|
| Class | class Name: | Blueprint for objects |
| Constructor | __init__(self) | Initialize object state |
| Encapsulation | _x / __x / @property | Control access to data |
| Inheritance | class Child(Parent) + super() | Reuse and extend |
| Polymorphism | method overriding / duck typing | Same call, many forms |
| Abstraction | ABC, @abstractmethod | Define a required interface |
| Dunder methods | __str__, __eq__, __add__ | Integrate with built-ins/operators |
Conclusion
OOP gives you a vocabulary for modelling real systems: classes describe things, objects are the things, and the four pillars — encapsulation, inheritance, polymorphism and abstraction — keep large codebases organized and reusable. Add dunder methods and your objects behave like first-class Python citizens.
Practice by modelling something you know: a playlist, a to-do list, a deck of cards. Once the four pillars click, frameworks like Django (models) and data libraries (custom classes) become far easier to read.
π¬ Comments (0)
No comments yet. Be the first to share your thoughts!