Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

Object-Oriented Programming in Python: The Four Pillars, Explained Without Jargon

Most Python tutorials introduce object-oriented programming (OOP) with a wall of definitions. This one tries a different approach: start with the problem that OOP solves, then show how each pillar addresses it.

Why OOP Exists

Procedural code — a sequence of instructions calling functions — works fine for small programs. As a program grows, you end up with functions that share data through global variables, long parameter lists, and no clear ownership over which code is responsible for what. Finding bugs becomes archaeological work.

OOP addresses this by grouping related data and the functions that operate on that data into a single unit called an object. Instead of asking “what does this function do to that data?”, you ask “what can this object do?”

The Four Pillars

1. Encapsulation

Encapsulation means bundling data with the methods that operate on it, and controlling what the outside world can see. You expose a clean interface and hide the messy internal state.

class Thermostat:
def __init__(self, current_temp):
self._temp = current_temp # protected — don't touch directly
def get_temperature(self):
return self._temp
def set_temperature(self, value):
if value < 0 or value > 40:
raise ValueError("Temperature out of safe range")
self._temp = value
t = Thermostat(21)
t.set_temperature(23)
print(t.get_temperature()) # 23

The validation logic lives inside the class. Any code that holds a Thermostat object gets consistent behaviour without needing to know how temperature is stored.

2. Inheritance

Inheritance lets one class (the child) reuse and extend the behaviour of another (the parent). This avoids copying code across similar classes.

class Vehicle:
def __init__(self, make, speed_limit):
self.make = make
self.speed_limit = speed_limit
def describe(self):
return f"{self.make} — max speed {self.speed_limit} km/h"
class ElectricCar(Vehicle):
def __init__(self, make, speed_limit, battery_kwh):
super().__init__(make, speed_limit)
self.battery_kwh = battery_kwh
def describe(self):
base = super().describe()
return f"{base}, battery {self.battery_kwh} kWh"
tesla = ElectricCar("Tesla Model 3", 225, 75)
print(tesla.describe())
# Tesla Model 3 — max speed 225 km/h, battery 75 kWh

ElectricCar inherits make and speed_limit from Vehicle and adds its own battery_kwh attribute. The describe method is extended, not replaced, using super().

3. Polymorphism

Polymorphism means “many forms”. Different classes can implement the same method name, and calling code does not need to know which class it’s working with — it just calls the method.

class Dog:
def speak(self):
return "Woof"
class Cat:
def speak(self):
return "Meow"
class Parrot:
def speak(self):
return "Squawk"
animals = [Dog(), Cat(), Parrot()]
for animal in animals:
print(animal.speak())
# Woof
# Meow
# Squawk

The loop calls speak() on each object without caring about its type. This is Python’s duck typing at work: if an object has the method, it works.

4. Abstraction

Abstraction means hiding the how and exposing only the what. You define what operations a class must support, leaving the implementation details to each subclass.

from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def generate(self):
"""Subclasses must implement this."""
class SalesReport(Report):
def generate(self):
return "Monthly sales: £42,000"
class InventoryReport(Report):
def generate(self):
return "Items in stock: 1,204"
# report = Report() # TypeError — can't instantiate abstract class
for r in [SalesReport(), InventoryReport()]:
print(r.generate())

The Report abstract class defines a contract. Any class that inherits from Report must implement generate(), or Python will refuse to instantiate it.

OOP vs Procedural: A Practical Comparison

Procedural approach for a bank account:

def deposit(balance, amount):
return balance + amount
def withdraw(balance, amount):
if amount > balance:
raise ValueError("Insufficient funds")
return balance - amount
balance = 1000
balance = deposit(balance, 500)

OOP approach:

class BankAccount:
def __init__(self, initial_balance):
self._balance = initial_balance
def deposit(self, amount):
self._balance += amount
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
@property
def balance(self):
return self._balance
account = BankAccount(1000)
account.deposit(500)
print(account.balance) # 1500

The OOP version keeps the balance protected inside the object. There is no separate balance variable floating around that unrelated code might accidentally overwrite.

Common Mistakes When Learning OOP

Overusing inheritance. Not every relationship is a parent-child relationship. A Dog is an Animal, but a Car does not inherit from Engine. When in doubt, prefer composition (an object that holds another object) over inheritance.

Making everything a class. A small utility function does not need to live inside a class. Python supports multiple styles; use the right one for the task.

Ignoring self. Every instance method must have self as its first parameter. Forgetting it causes a TypeError when you call the method.

Putting business logic in __init__. The constructor should set up the object’s initial state. Heavy computation, file reading, or network calls belong elsewhere.

Summary

PillarWhat It Does
EncapsulationBundles data and methods; controls access
InheritanceLets subclasses reuse parent class behaviour
PolymorphismSame method name, different behaviour per class
AbstractionDefines what a class must do without specifying how

OOP does not make programs automatically better. It provides a structure that scales well when a codebase grows and multiple developers work on it. Understanding these four pillars gives you the vocabulary to design Python programs that stay manageable as they get more complex.

Next > Classes and Objects