Skip to content

Abstraction in Python

What is Abstraction?

Abstraction means showing only WHAT an object does and hiding HOW it does it. The user works with a simple interface and does not need to know the complicated details inside.

Real-Life Analogy

Driving a car: you use the steering wheel, accelerator and brake. You do not need to know how fuel injection or the gearbox works.

How Do We Do It in Python?

Using the built-in "abc" module (Abstract Base Classes):

from abc import ABC, abstractmethod
class Shape(ABC):              <- abstract class (a blueprint/contract)
    @abstractmethod
    def area(self):            <- abstract method (no real body)
        pass

Rules

  • An abstract class CANNOT be instantiated (you cannot do Shape()).
  • A child class MUST implement ALL abstract methods, or it also stays abstract and cannot be instantiated.
  • An abstract class CAN also have normal (concrete) methods and a constructor, which children inherit.

ABSTRACTION vs ENCAPSULATION (common interview question) -------------------------------------------------------- Abstraction -> hides COMPLEXITY. Focus: WHAT it does (design level). Encapsulation -> hides DATA. Focus: protecting state (implementation level).

Why We Need Abstraction

Without abstraction, nothing forces a developer to write the method that the rest of the system expects. The mistake shows up only when the code runs (maybe in production!).

class Notifier:                       # a normal (non-abstract) parent
    def send(self, msg):
        pass                          # does nothing - easy to forget to override


class SmsNotifier(Notifier):
    def sned(self, msg):              # TYPO! send() was never overridden
        print("SMS:", msg)


SmsNotifier().send("OTP is 1234")     # silently does NOTHING - a hidden bug
print("  SmsNotifier().send() ran but sent nothing. The bug went unnoticed!")
print("  Abstraction catches this kind of mistake early. See Section 4.")

Creating an Abstract Class

1. Inherit from ABC. 2. Mark the required methods with @abstractmethod. Industry example: every shape in a design tool must calculate its area and perimeter.

class Shape(ABC):
    @abstractmethod
    def area(self):
        """Every shape MUST say how to calculate its area."""

    @abstractmethod
    def perimeter(self):
        """Every shape MUST say how to calculate its perimeter."""


class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h

    def area(self):
        return self.w * self.h

    def perimeter(self):
        return 2 * (self.w + self.h)


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

    def area(self):
        return round(3.14159 * self.r ** 2, 2)

    def perimeter(self):
        return round(2 * 3.14159 * self.r, 2)


for shape in (Rectangle(4, 5), Circle(3)):
    print(f"  {type(shape).__name__:<10} area={shape.area():<8} perimeter={shape.perimeter()}")

An Abstract Class Cannot Be Instantiated

try:
    s = Shape()
except TypeError as err:
    print("  ERROR:", err)

A Child Must Implement All Abstract Methods

If even ONE abstract method is missing, Python refuses to create the object. This is the same typo as Section 1, but now it is caught immediately at object creation instead of hiding silently.

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

    def perimeter(self):
        return self.a + self.b + self.c
    # area() is missing!


try:
    t = Triangle(3, 4, 5)
except TypeError as err:
    print("  ERROR:", err)

Abstract Class with Constructor + Concrete Methods

An abstract class can hold COMMON data and COMMON code. Children reuse it and only fill in the parts that differ. Industry example: employee payroll. Every employee has an id and a name, and payslip printing is the same. Only salary calculation differs.

class Employee(ABC):
    def __init__(self, emp_id, name):          # constructor in an abstract class
        self.emp_id = emp_id
        self.name = name

    @abstractmethod
    def calculate_salary(self):
        """Each employee type calculates salary differently."""

    def print_payslip(self):                   # CONCRETE method, shared by all
        print(f"  Payslip | {self.emp_id} | {self.name:<8} | Rs.{self.calculate_salary():,}")


class FullTimeEmployee(Employee):
    def __init__(self, emp_id, name, monthly_salary):
        super().__init__(emp_id, name)
        self.monthly_salary = monthly_salary

    def calculate_salary(self):
        return self.monthly_salary


class ContractEmployee(Employee):
    def __init__(self, emp_id, name, hours, rate):
        super().__init__(emp_id, name)
        self.hours = hours
        self.rate = rate

    def calculate_salary(self):
        return self.hours * self.rate


class Intern(Employee):
    def calculate_salary(self):
        return 15000                            # fixed stipend


staff = [
    FullTimeEmployee("E01", "Ramesh", 80000),
    ContractEmployee("C01", "Fatima", 120, 500),
    Intern("I01", "Joseph"),
]
for person in staff:
    person.print_payslip()

Template Method Pattern

The abstract class fixes the ORDER of steps (the workflow) in one concrete method. Children only fill in the individual steps. Industry example: data export (ETL). Every export must

read data -> transform it -> write it, always in that order.
class DataExporter(ABC):
    def export(self):                       # the TEMPLATE - fixed workflow
        print(f"  --- {type(self).__name__} ---")
        data = self.read()
        data = self.transform(data)
        self.write(data)

    @abstractmethod
    def read(self): ...

    @abstractmethod
    def write(self, data): ...

    def transform(self, data):              # optional hook with a default
        return data


class CsvExporter(DataExporter):
    def read(self):
        print("  reading rows from database")
        return [("Anu", 90), ("Babu", 75)]

    def write(self, data):
        for name, marks in data:
            print(f"  CSV  -> {name},{marks}")


class JsonExporter(DataExporter):
    def read(self):
        print("  reading rows from database")
        return [("Anu", 90), ("Babu", 75)]

    def transform(self, data):              # overrides the optional hook
        print("  transforming rows into dictionaries")
        return [{"name": n, "marks": m} for n, m in data]

    def write(self, data):
        print(f"  JSON -> {data}")


for exporter in (CsvExporter(), JsonExporter()):
    exporter.export()

Abstract Properties

We can force children to provide a PROPERTY, not just a method, by stacking @property and @abstractmethod. Industry example: every subscription plan must declare its price.

class SubscriptionPlan(ABC):
    @property
    @abstractmethod
    def monthly_price(self):
        """Each plan must define its monthly price."""

    def yearly_price(self):
        return self.monthly_price * 12 * 0.8          # 20% yearly discount


class BasicPlan(SubscriptionPlan):
    @property
    def monthly_price(self):
        return 199


class PremiumPlan(SubscriptionPlan):
    @property
    def monthly_price(self):
        return 649


for plan in (BasicPlan(), PremiumPlan()):
    print(f"  {type(plan).__name__:<12} monthly Rs.{plan.monthly_price} | yearly Rs.{plan.yearly_price():.0f}")

Interfaces (Only Abstract Methods)

Python has no "interface" keyword. An abstract class with ONLY abstract methods acts as an interface. A class can implement many interfaces through multiple inheritance. Industry example: devices in a smart-home app.

class Switchable(ABC):
    @abstractmethod
    def turn_on(self): ...

    @abstractmethod
    def turn_off(self): ...


class Dimmable(ABC):
    @abstractmethod
    def set_brightness(self, level): ...


class SmartBulb(Switchable, Dimmable):        # implements TWO interfaces
    def turn_on(self):
        print("  Bulb ON")

    def turn_off(self):
        print("  Bulb OFF")

    def set_brightness(self, level):
        print(f"  Bulb brightness set to {level}%")


class SmartFan(Switchable):                   # implements ONE interface
    def turn_on(self):
        print("  Fan ON")

    def turn_off(self):
        print("  Fan OFF")


bulb, fan = SmartBulb(), SmartFan()
for device in (bulb, fan):                    # every Switchable can be turned on
    device.turn_on()
bulb.set_brightness(60)
print("  Is fan Dimmable? ", isinstance(fan, Dimmable))
print("  Is bulb Dimmable?", isinstance(bulb, Dimmable))

Industry Example - Payment Gateway

An online shop supports many payment providers. The checkout code only knows the abstract PaymentGateway. It never needs to know the details of each provider (API keys, request formats, refunds...). To add a new provider, write one new class. Checkout stays untouched.

class PaymentGateway(ABC):
    def __init__(self, merchant_id):
        self.merchant_id = merchant_id
        self._transactions = []

    @abstractmethod
    def _authenticate(self):
        """Provider-specific login/handshake (hidden detail)."""

    @abstractmethod
    def _charge(self, amount):
        """Provider-specific charge. Returns a transaction id."""

    def process_payment(self, amount):          # the ONE simple public method
        if amount <= 0:
            print("  Invalid amount")
            return None
        self._authenticate()
        txn_id = self._charge(amount)
        self._transactions.append(txn_id)
        print(f"  SUCCESS: Rs.{amount} | txn={txn_id}")
        return txn_id


class CardGateway(PaymentGateway):
    def _authenticate(self):
        print("  [Card] verifying API key and 3-D Secure")

    def _charge(self, amount):
        print(f"  [Card] charging card for Rs.{amount}")
        return f"CARD-{len(self._transactions) + 1:04d}"


class UpiGateway(PaymentGateway):
    def _authenticate(self):
        print("  [UPI] validating VPA and sending collect request")

    def _charge(self, amount):
        print(f"  [UPI] user approved Rs.{amount} in UPI app")
        return f"UPI-{len(self._transactions) + 1:04d}"


class WalletGateway(PaymentGateway):
    def _authenticate(self):
        print("  [Wallet] checking wallet session")

    def _charge(self, amount):
        print(f"  [Wallet] debiting Rs.{amount} from wallet")
        return f"WAL-{len(self._transactions) + 1:04d}"


def checkout(gateway: PaymentGateway, amount):
    """Checkout depends only on the ABSTRACT class, not on any provider."""
    print(f"  Checkout using {type(gateway).__name__}")
    gateway.process_payment(amount)


for gw in (CardGateway("M-1"), UpiGateway("M-1"), WalletGateway("M-1")):
    checkout(gw, 2499)

Quick Revision

  • Abstraction = expose WHAT, hide HOW.
  • from abc import ABC, abstractmethod
  • Abstract class cannot be instantiated.
  • Child must implement EVERY abstract method or it stays abstract.
  • Abstract classes may have a constructor, concrete methods and abstract properties.
  • Template Method: the parent fixes the workflow, children fill in steps.
  • Code against the abstract class (PaymentGateway), not the details.

Practice Questions

  1. Create an abstract class Vehicle with abstract methods wheels() and
  2. Create an abstract Database class with connect(), query(), close().
  3. Create an abstract Report class with a template method generate()