Python Abstraction Explained for Beginners | OOP Complete Guide

Python Abstraction – Hiding Complexity and Showing Only What Matters

📅 Published on Code with Py📂 Category: Python OOP⏱️ Reading time: ~14 min

You have now learned three of the four pillars of OOP — Classes and Objects, Inheritance, Polymorphism, and Encapsulation. Today you learn the fourth and final pillar — Abstraction. When you drive a car, you press the accelerator and the car moves. You don't think about fuel injection, engine torque, or gear ratios. That complexity is hidden from you — you only see what you need to use. That is abstraction. In programming, it means designing classes that show the user what to do without forcing them to understand how it works inside. This guide explains it from scratch with real examples and practical Python code.

📋 Table of Contents

  1. What Is Abstraction? (And Why It Feels Familiar)
  2. Real-Life Analogy — The TV Remote
  3. The Four Pillars of OOP — Where Abstraction Fits
  4. Abstraction vs Encapsulation — People Confuse These
  5. Abstract Classes in Python — The abc Module
  6. Your First Abstract Class
  7. Step-by-Step Trace — What Happens When Rules Are Broken
  8. Abstract Methods — The Contract Every Child Must Sign
  9. Concrete Methods Inside Abstract Classes
  10. Real-Life Project — Payment Gateway System
  11. Multiple Abstract Methods
  12. Practice Problems
  13. FAQ
  14. Summary

1. What Is Abstraction? (And Why It Feels Familiar)

Here is something interesting — you already use abstraction every single day without realising it.

When you send a WhatsApp message, you tap the send button. You have no idea what happens after that — which server your message travels through, how it gets encrypted, how it finds the recipient's phone. All of that complexity is hidden. You just tap send and the message arrives.

When you use Python's print() function, you don't read the C source code that makes it work. You just call print and your text appears. The complexity is abstracted away.

In OOP, abstraction means designing your classes so that the user of the class only sees what they need to see — and all the internal complexity is hidden behind a clean, simple interface.

Without Abstraction

User must understand all internal logic to use your class. Change the internals and every user's code might break. Code is tightly coupled and hard to swap out.

With Abstraction

User only needs to know what methods exist and what they do. Change internals freely — user's code stays the same. Easy to swap one implementation for another.


2. Real-Life Analogy — The TV Remote

📺 You Use It Without Understanding It

You point a remote at your TV and press the volume button. Your TV gets louder. You have no idea that pressing that button sends an infrared signal at a specific frequency, which the TV's sensor decodes into a command, which triggers the audio driver to increase the output gain.

You don't need to know any of that. The remote gives you a simple interface — buttons with labels. The complexity lives inside the TV, hidden from you completely.

In Python, an abstract class is like the remote. It defines the buttons (abstract methods) that must exist. Each TV brand (each child class) decides how those buttons work internally. The user just presses buttons and gets results — no knowledge of internals required.


3. The Four Pillars of OOP — Where Abstraction Fits

✅ Classes & Objects
✅ Inheritance
✅ Polymorphism
✅ Encapsulation
🎯 Abstraction — You Are Here (Final Pillar!)

Abstraction works closely with Encapsulation and Polymorphism. In fact, many real programs use all four pillars together — you will see exactly how in the project section below.


4. Abstraction vs Encapsulation — People Confuse These

This is one of the most common confusions in OOP — even experienced programmers mix these up. They are related but solve different problems.

FeatureAbstractionEncapsulation
Core ideaHide complexity — show only what mattersHide data — protect it from direct access
Focuses onDesign level — what interface to exposeImplementation level — how to protect attributes
Tool usedAbstract classes, abstract methodsPrivate/protected attributes, getters, setters
Question it answers"What should the user be able to do?""Who should be allowed to change this data?"
ExampleAll payment types have a pay() method — don't care howBalance is private — only deposit/withdraw can change it
A simple way to remember the difference: Encapsulation hides data. Abstraction hides logic. Encapsulation says "you can't touch my balance directly." Abstraction says "you don't need to know how I process payments — just call pay()."

5. Abstract Classes in Python — The abc Module

Python provides abstraction through its built-in abc module — which stands for Abstract Base Classes. You use two things from this module — the ABC class (which your abstract class inherits from) and the @abstractmethod decorator (which marks a method as required in all children).

from abc import ABC, abstractmethod

class MyAbstractClass(ABC):   # inherit ABC to make it abstract

    @abstractmethod
    def my_method(self):         # every child MUST implement this
        pass

Two things make a class abstract in Python. First, it inherits from ABC. Second, at least one of its methods has the @abstractmethod decorator. Any class that meets both these conditions cannot be created directly — Python will refuse.


6. Your First Abstract Class

Let us build something real. Imagine you are designing a system where different shapes all need to calculate area and perimeter. You want to guarantee that every shape class implements these two methods — no shape should exist without them.

from abc import ABC, abstractmethod

class Shape(ABC):
    """Blueprint for all shapes. Cannot be used directly."""

    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

    def perimeter(self):
        return 2 * 3.14159 * self.radius


class Rectangle(Shape):
    def __init__(self, length, width):
        self.length = length
        self.width  = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * (self.length + self.width)


# Use the shapes — clean interface, hidden complexity
shapes = [Circle(7), Rectangle(5, 4)]

for s in shapes:
    print(f"{s.__class__.__name__}:")
    print(f"  Area:      {s.area():.2f}")
    print(f"  Perimeter: {s.perimeter():.2f}\n")
Circle:
  Area:      153.94
  Perimeter: 43.98

Rectangle:
  Area:      20.00
  Perimeter: 18.00
# Try to create the abstract class directly — Python refuses
s = Shape()
TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter
The abstract class Shape is a contract. It says — "any class that calls itself a Shape must implement both area() and perimeter(). If you skip even one, Python will not let you create an object from that class." This is abstraction enforcing good design.

7. Step-by-Step Trace — What Happens When Rules Are Broken

Trace of: what happens when a child class skips an abstract method
1
You define: class Triangle(Shape): — inherits from Shape
2
Triangle only implements area() — forgets perimeter()
3
You write: t = Triangle(3, 4, 5) — try to create object
4
Python checks: does Triangle implement ALL abstract methods from Shape?
5
area() → found ✓ | perimeter() → missing ✗
6
Python raises TypeError BEFORE the object is even created
Error: Can't instantiate abstract class Triangle with abstract method perimeter
class Triangle(Shape):
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c

    def area(self):                   # only area implemented
        s = (self.a + self.b + self.c) / 2
        return (s*(s-self.a)*(s-self.b)*(s-self.c)) ** 0.5
    # perimeter() forgotten!

t = Triangle(3, 4, 5)
TypeError: Can't instantiate abstract class Triangle with abstract method perimeter

Now add the missing method and it works:

class Triangle(Shape):
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c

    def area(self):
        s = (self.a + self.b + self.c) / 2
        return (s*(s-self.a)*(s-self.b)*(s-self.c)) ** 0.5

    def perimeter(self):
        return self.a + self.b + self.c

t = Triangle(3, 4, 5)
print(f"Triangle Area:      {t.area():.2f}")
print(f"Triangle Perimeter: {t.perimeter():.2f}")
Triangle Area:      6.00
Triangle Perimeter: 12.00

8. Abstract Methods — The Contract Every Child Must Sign

Here is a good way to think about abstract methods — they are like job requirements in a hiring notice. If you apply to be a "Driver", you must know how to drive. If you apply to be a "Chef", you must know how to cook. These are non-negotiable requirements.

An abstract method is a non-negotiable requirement for any child class. The parent defines what abilities every child must have. The child decides how those abilities work in practice.

from abc import ABC, abstractmethod

class Vehicle(ABC):
    """Every vehicle must define how it starts and how it moves."""

    @abstractmethod
    def start_engine(self):
        pass

    @abstractmethod
    def move(self):
        pass

    @abstractmethod
    def fuel_type(self):
        pass


class PetrolCar(Vehicle):
    def start_engine(self):
        print("Petrol car: Vroom! Engine starts with ignition.")

    def move(self):
        print("Petrol car: Accelerating on fuel-powered engine.")

    def fuel_type(self):
        print("Uses: Petrol")


class ElectricCar(Vehicle):
    def start_engine(self):
        print("Electric car: Silent start. Press button.")

    def move(self):
        print("Electric car: Gliding on battery power.")

    def fuel_type(self):
        print("Uses: Electricity")


class Bicycle(Vehicle):
    def start_engine(self):
        print("Bicycle: No engine. Start pedalling!")

    def move(self):
        print("Bicycle: Moving by human pedal power.")

    def fuel_type(self):
        print("Uses: Human energy")


# Same interface — no matter which vehicle
vehicles = [PetrolCar(), ElectricCar(), Bicycle()]

for v in vehicles:
    print(f"\n--- {v.__class__.__name__} ---")
    v.start_engine()
    v.move()
    v.fuel_type()
--- PetrolCar ---
Petrol car: Vroom! Engine starts with ignition.
Petrol car: Accelerating on fuel-powered engine.
Uses: Petrol

--- ElectricCar ---
Electric car: Silent start. Press button.
Electric car: Gliding on battery power.
Uses: Electricity

--- Bicycle ---
Bicycle: No engine. Start pedalling!
Bicycle: Moving by human pedal power.
Uses: Human energy

9. Concrete Methods Inside Abstract Classes

An abstract class is not only made of abstract methods. It can also have concrete methods — fully implemented methods that all children inherit and use as-is. This is a powerful combination — the abstract class enforces some methods (abstract) and provides others for free (concrete).

from abc import ABC, abstractmethod

class Animal(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def speak(self):             # Must be implemented by every child
        pass

    def breathe(self):           # Concrete — all animals breathe the same way
        print(f"{self.name} inhales oxygen and exhales carbon dioxide.")

    def introduce(self):        # Concrete — shared by all animals
        print(f"I am {self.name}, a {self.__class__.__name__}.")
        self.speak()
        self.breathe()


class Dog(Animal):
    def speak(self):
        print(f"{self.name} barks: Woof Woof!")


class Cat(Animal):
    def speak(self):
        print(f"{self.name} meows: Meow!")


d = Dog("Bruno")
c = Cat("Whiskers")

d.introduce()
print()
c.introduce()
I am Bruno, a Dog.
Bruno barks: Woof Woof!
Bruno inhales oxygen and exhales carbon dioxide.

I am Whiskers, a Cat.
Whiskers meows: Meow!
Whiskers inhales oxygen and exhales carbon dioxide.
The concrete method introduce() calls self.speak() — even though speak() is abstract in Animal. This works because by the time introduce() runs, self is always a Dog or Cat — never a bare Animal. The child's version of speak() is what runs.

10. Real-Life Project — Payment Gateway System

This project brings together all four OOP pillars you have learned — abstraction defines the interface, inheritance shares common code, polymorphism lets one loop handle all payment types, and encapsulation protects transaction data.

from abc import ABC, abstractmethod

class PaymentGateway(ABC):
    """Abstract gateway. Defines what every payment method must do."""
    def __init__(self, customer, amount):
        self.customer     = customer
        self.__amount     = amount   # encapsulated
        self.__status     = "pending"

    @property
    def amount(self):
        return self.__amount

    @abstractmethod
    def validate(self):         # abstract — each method validates differently
        pass

    @abstractmethod
    def process_payment(self):  # abstract — each method processes differently
        pass

    def execute(self):          # concrete — same flow for all payment types
        print(f"\n{'─'*40}")
        print(f"Customer : {self.customer}")
        print(f"Amount   : ₹{self.__amount}")
        print(f"Method   : {self.__class__.__name__}")
        print(f"{'─'*40}")
        if self.validate():
            self.process_payment()
            self.__status = "success"
            print(f"✅ Payment Successful!")
        else:
            self.__status = "failed"
            print(f"❌ Payment Failed — Validation Error")
        print(f"Status   : {self.__status.upper()}")


class UPIPayment(PaymentGateway):
    def __init__(self, customer, amount, upi_id):
        super().__init__(customer, amount)
        self.upi_id = upi_id

    def validate(self):
        valid = "@" in self.upi_id and self.amount > 0
        print(f"UPI ID   : {self.upi_id}")
        print(f"Valid?   : {'Yes' if valid else 'No'}")
        return valid

    def process_payment(self):
        print(f"Sending ₹{self.amount} to UPI ID: {self.upi_id}")


class CardPayment(PaymentGateway):
    def __init__(self, customer, amount, card_no):
        super().__init__(customer, amount)
        self.__card_no = card_no

    def validate(self):
        valid = len(self.__card_no) == 16 and self.amount > 0
        print(f"Card     : ****{self.__card_no[-4:]}")
        print(f"Valid?   : {'Yes' if valid else 'No'}")
        return valid

    def process_payment(self):
        print(f"Charging ₹{self.amount} to card ****{self.__card_no[-4:]}")


class CODPayment(PaymentGateway):
    def validate(self):
        print("COD — No pre-validation needed.")
        return True

    def process_payment(self):
        print(f"Order confirmed. Collect ₹{self.amount} on delivery.")


# Execute all payments through the same interface
payments = [
    UPIPayment("Rohan",  1500, "rohan@oksbi"),
    CardPayment("Priya", 3200, "1234567890124567"),  # wrong length
    CODPayment("Arjun",  800),
]

for p in payments:
    p.execute()
────────────────────────────────────────
Customer : Rohan
Amount   : ₹1500
Method   : UPIPayment
────────────────────────────────────────
UPI ID   : rohan@oksbi
Valid?   : Yes
Sending ₹1500 to UPI ID: rohan@oksbi
✅ Payment Successful!
Status   : SUCCESS

────────────────────────────────────────
Customer : Priya
Amount   : ₹3200
Method   : CardPayment
────────────────────────────────────────
Card     : ****4567
Valid?   : No
❌ Payment Failed — Validation Error
Status   : FAILED

────────────────────────────────────────
Customer : Arjun
Amount   : ₹800
Method   : CODPayment
────────────────────────────────────────
COD — No pre-validation needed.
Order confirmed. Collect ₹800 on delivery.
✅ Payment Successful!
Status   : SUCCESS

11. Multiple Abstract Methods — A Complete Example

from abc import ABC, abstractmethod

class DatabaseConnector(ABC):
    """Every database must implement connect, query, and close."""

    @abstractmethod
    def connect(self): pass

    @abstractmethod
    def query(self, sql): pass

    @abstractmethod
    def close(self): pass

    def run(self, sql):    # concrete — same for all databases
        self.connect()
        result = self.query(sql)
        self.close()
        return result


class MySQLConnector(DatabaseConnector):
    def connect(self):
        print("MySQL: Connected via port 3306")

    def query(self, sql):
        print(f"MySQL: Running → {sql}")
        return "[MySQL Result]"

    def close(self):
        print("MySQL: Connection closed\n")


class MongoConnector(DatabaseConnector):
    def connect(self):
        print("MongoDB: Connected via port 27017")

    def query(self, sql):
        print(f"MongoDB: Running → {sql}")
        return "[Mongo Result]"

    def close(self):
        print("MongoDB: Connection closed\n")

mysql = MySQLConnector()
mongo = MongoConnector()

mysql.run("SELECT * FROM students")
mongo.run("db.students.find({})")
MySQL: Connected via port 3306
MySQL: Running → SELECT * FROM students
MySQL: Connection closed

MongoDB: Connected via port 27017
MongoDB: Running → db.students.find({})
MongoDB: Connection closed

12. Practice Problems

Beginner level

  1. Create an abstract class Shape with abstract methods area() and perimeter(). Add Circle, Square, Triangle children.
  2. Create an abstract class Animal with abstract speak(). Add Dog, Cat, Cow children.
  3. Prove that creating an abstract class directly raises TypeError.
  4. Add a concrete method to your abstract class that all children share.
  5. Create an abstract class Employee with abstract salary(). Add FullTime and PartTime children.

Intermediate level

  1. Build a Notification abstract class — abstract send() method. Add EmailNotification, SMSNotification, PushNotification children.
  2. Create a Logger abstract class with abstract log(). Add FileLogger and ConsoleLogger.
  3. Build an abstract Sorter class with abstract sort() method. Implement BubbleSorter and SelectionSorter children.
  4. Create a Report abstract class — abstract generate() method — and a concrete save() method that calls generate().
  5. Use @abstractmethod on a property instead of a method.

Real-life projects

  1. Build the complete payment gateway from this post from memory.
  2. Create a DatabaseConnector with MySQL and SQLite children.
  3. Build a Vehicle abstract class — all four OOP pillars in one project.
  4. Create a CloudStorage abstract class — upload(), download(), delete() — with GoogleDrive and Dropbox children.
  5. Build a Game abstract class — start(), pause(), save(), load() — with Chess and TicTacToe children.

Think deeper

  1. Can an abstract class inherit from another abstract class?
  2. What happens if a child class is also abstract?
  3. Can you put @abstractmethod on __init__?
  4. What is the difference between pass and raise NotImplementedError in abstract methods?
  5. How does abstraction help when you need to swap one database for another in a large project?

13. FAQ

Q1. What exactly is abstraction in Python OOP?

Abstraction is hiding the internal complexity of a class and exposing only the essential operations that users need. In Python, you achieve this using abstract classes — which define what methods must exist without specifying how they work. Each child class then provides its own implementation of those methods. The user interacts with a clean, simple interface without needing to know anything about the internals.

Q2. Can an abstract class have a constructor?

Yes. An abstract class can have an __init__ method and the child can call it using super().__init__() — just like regular inheritance. The constructor in an abstract class is useful for setting common attributes that all children will share — like the customer name and amount in the PaymentGateway example above.

Q3. What is the difference between an abstract class and a regular parent class?

A regular parent class can be instantiated directly and provides default implementations for all its methods. An abstract class cannot be instantiated — it is a blueprint only. It can have abstract methods (which children must implement) and concrete methods (which children inherit as-is). The key difference is enforcement — abstract classes force children to implement specific methods.

Q4. What happens if a child class does not implement all abstract methods?

Python raises a TypeError when you try to create an object from that child class. The error message tells you exactly which abstract methods are missing. This happens at object creation time — not when you call the method — which catches bugs early and prevents incomplete implementations from being used.

Q5. Is there a difference between using ABC and raising NotImplementedError?

Yes — an important one. Raising NotImplementedError is a manual approach — the error only happens when the method is actually called at runtime. Using @abstractmethod with ABC is stricter — Python refuses to create the object at all if any abstract method is missing, before any method is even called. ABC gives you earlier and clearer error detection.

Q6. Can an abstract class inherit from another abstract class?

Yes. An abstract class can inherit from another abstract class and add more abstract methods or inherit the existing ones without implementing them. A concrete class further down the chain must implement ALL abstract methods from the entire inheritance chain before it can be instantiated. This is useful for building layered interfaces in large systems.


✅ Quick Summary — What You Learned

  • Abstraction hides complexity and shows only what the user needs to interact with
  • In Python, abstraction is implemented using the abc module — ABC class and @abstractmethod decorator
  • An abstract class cannot be instantiated directly — it is a blueprint for child classes
  • Abstract methods have no body — they define what must exist, not how it works
  • Every child class must implement all abstract methods — or Python raises TypeError at object creation
  • Abstract classes can also have concrete methods — these are inherited as-is by all children
  • Abstraction + Inheritance + Polymorphism + Encapsulation = all four OOP pillars complete
  • The payment gateway project shows how all four pillars work together in one real system

"Abstraction is the art of knowing what to show and what to hide — the best interfaces feel like magic because all the hard work is invisible."

— Code with Py

You have now completed all four pillars of Python OOP! Build the payment gateway project from scratch on your own — it brings everything together beautifully. Drop your version in the comments!

Comments

Popular posts from this blog

Python Strings – A Complete Guide for Beginners (with Examples)

Python Control Flow Statements – Complete Guide with Real-Life Examples

Python Loops Explained – for Loop and while Loop with Examples and Output