
Python Course
Abstraction in Python
Abstraction is the fourth pillar of object-oriented programming. Where encapsulation hides data, abstraction hides complexity. When you drive a car, you use a steering wheel and pedals — you do not think about the combustion cycle or transmission ratios. The complex internals are hidden behind a simple, consistent interface. That is abstraction.
In Python, abstraction is achieved primarily through abstract classes — classes that define a required interface without providing a full implementation. They enforce a contract at class design time rather than discovering missing methods at runtime.
The Problem Abstraction Solves
Without abstraction, a base class can only raise NotImplementedError at runtime — after an instance is already created and code is already running. Abstract classes catch the problem earlier, at instantiation time.
# Without abstraction — error only discovered at runtime
class Shape:
def area(self):
raise NotImplementedError("Subclasses must implement area()")
class Circle(Shape):
pass # forgot to implement area()
c = Circle() # no error yet — instance created fine
c.area() # NotImplementedError raised only now, at call timeWith an abstract class, Circle() itself raises a TypeError immediately — catching the mistake at the point of instantiation, before any other code runs.
The abc Module — Abstract Base Classes
Python's abc module provides ABC and the @abstractmethod decorator. Any class inheriting from ABC with at least one @abstractmethod cannot be instantiated directly.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
"""Return the area of the shape."""
pass
@abstractmethod
def perimeter(self):
"""Return the perimeter of the shape."""
pass
def describe(self): # concrete method — inherited by all subclasses
print(f"{self.__class__.__name__}: area={self.area():.2f}, perimeter={self.perimeter():.2f}")
# Instantiating the abstract class raises TypeError immediately
try:
s = Shape()
except TypeError as e:
print("Error:", e)
# A subclass that misses an abstract method is also abstract
class IncompleteCircle(Shape):
def area(self): # only implements one of the two
return 3.14 * 5 ** 2
try:
ic = IncompleteCircle()
except TypeError as e:
print("Error:", e)- Inherit from
ABCto make a class abstract:class MyClass(ABC): - Mark required methods with
@abstractmethod— subclasses must override every one. - A subclass that does not implement all abstract methods is itself abstract and cannot be instantiated.
- Abstract classes can contain concrete methods — fully inherited by subclasses.
Implementing the Abstract Interface
import math
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self): pass
@abstractmethod
def perimeter(self): pass
def describe(self):
print(f"{self.__class__.__name__}: area={self.area():.2f}, perimeter={self.perimeter():.2f}")
def is_larger_than(self, other):
return self.area() > other.area()
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
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 Triangle(Shape):
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def area(self):
s = self.perimeter() / 2
return math.sqrt(s * (s-self.a) * (s-self.b) * (s-self.c))
def perimeter(self):
return self.a + self.b + self.c
shapes = [Circle(5), Rectangle(4, 6), Triangle(3, 4, 5)]
for s in shapes:
s.describe()
# Concrete method defined in ABC works for all shapes
largest = max(shapes, key=lambda s: s.area())
print(f"Largest: {largest.__class__.__name__} (area {largest.area():.2f})")- The abstract base guarantees
area()andperimeter()exist on every shape. describe()andis_larger_than()are defined once in the base and work for all subclasses.- Adding a
Pentagonclass requires only writing the new class — no existing code changes.
Abstract Properties
Combine @property with @abstractmethod to require that subclasses expose specific attributes as properties. Stack @property above @abstractmethod — the order matters.
from abc import ABC, abstractmethod
class Vehicle(ABC):
@property
@abstractmethod
def fuel_type(self):
pass
@property
@abstractmethod
def max_speed(self):
pass
@abstractmethod
def start_engine(self):
pass
def describe(self): # concrete — shared by all vehicles
print(f"{self.__class__.__name__} | {self.fuel_type} | {self.max_speed} km/h")
class ElectricCar(Vehicle):
@property
def fuel_type(self): return "Electric"
@property
def max_speed(self): return 250
def start_engine(self): print("Silent motor hum...")
class PetrolBike(Vehicle):
@property
def fuel_type(self): return "Petrol"
@property
def max_speed(self): return 200
def start_engine(self): print("Vroom!")
for v in [ElectricCar(), PetrolBike()]:
v.start_engine()
v.describe()Real-World Example — Payment Gateway
Abstract classes shine in plugin-style architectures — a core system defines the interface, and different implementations are swapped in without touching the core.
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
@abstractmethod
def charge(self, amount: float, currency: str) -> bool:
pass
@abstractmethod
def refund(self, transaction_id: str) -> bool:
pass
@abstractmethod
def get_transaction_fee(self, amount: float) -> float:
pass
def process(self, amount, currency="USD"): # concrete — shared logic
fee = self.get_transaction_fee(amount)
total = round(amount + fee, 2)
print(f"Processing ${total:.2f} {currency} (fee ${fee:.2f})")
return self.charge(total, currency)
class StripeGateway(PaymentGateway):
def charge(self, amount, currency):
print(f" Stripe: charged ${amount:.2f} {currency}"); return True
def refund(self, tid):
print(f" Stripe: refunded {tid}"); return True
def get_transaction_fee(self, amount):
return round(amount * 0.029 + 0.30, 2)
class PayPalGateway(PaymentGateway):
def charge(self, amount, currency):
print(f" PayPal: charged ${amount:.2f} {currency}"); return True
def refund(self, tid):
print(f" PayPal: refunded {tid}"); return True
def get_transaction_fee(self, amount):
return round(amount * 0.0349, 2)
class SquareGateway(PaymentGateway):
def charge(self, amount, currency):
print(f" Square: charged ${amount:.2f} {currency}"); return True
def refund(self, tid):
print(f" Square: refunded {tid}"); return True
def get_transaction_fee(self, amount):
return round(amount * 0.026 + 0.10, 2)
for gateway in [StripeGateway(), PayPalGateway(), SquareGateway()]:
gateway.process(100.00)
print()- The abstract base guarantees every gateway has
charge(),refund(), andget_transaction_fee(). process()is implemented once and works correctly for all gateways — addingSquareGatewayrequired zero changes to existing code.- Swapping from Stripe to PayPal changes one line — the rest of the system is untouched.
Abstraction vs Encapsulation
- Encapsulation — hides data inside a class and controls access through a defined interface.
- Abstraction — hides complexity by defining what an object does without specifying how — focuses on the interface, not the internals.
- They work together: an abstract class defines the interface (abstraction), while each concrete implementation controls access to its own data (encapsulation).
Quick Reference Table
| Concept | What It Does | Key Syntax |
|---|---|---|
ABC | Base class that enables abstract method enforcement | class MyClass(ABC): |
@abstractmethod | Marks a method every subclass must implement | @abstractmethod def m(self): pass |
| Abstract property | Requires a property implementation in every subclass | @property @abstractmethod |
| Concrete method in ABC | Shared logic available to all subclasses | Regular method alongside abstract ones |
| Instantiation guard | Prevents creating an incomplete object | Raised as TypeError at instantiation time |
| Partial implementation | Subclass missing abstract methods stays abstract | Also raises TypeError on instantiation |
Practice
Which module provides ABC and @abstractmethod in Python?
What exception does Python raise when you try to instantiate an abstract class directly?
Can an abstract base class contain concrete (non-abstract) methods?
What is the correct decorator order when defining an abstract property?
What is the key difference between abstraction and encapsulation?
What happens if a subclass implements only some of the abstract methods?
Quick Quiz
What happens if a subclass of an abstract class does not implement all abstract methods?
What is the advantage of @abstractmethod over just raising NotImplementedError?
In the payment gateway example, which method is defined in the abstract base and shared by all subclasses?
Which of the following is true about abstract base classes in Python?
What design principle does the payment gateway example directly support?
What is the name of the Python standard library module that provides abstract base class support?