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
- Create an abstract class Vehicle with abstract methods
wheels()and - Create an abstract Database class with
connect(),query(),close(). - Create an abstract Report class with a template method
generate()