Abstraction in Python | Python Course | Dataplexa

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 time
NotImplementedError: Subclasses must implement area()

With 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)
Error: Can't instantiate abstract class Shape with abstract method perimeter Error: Can't instantiate abstract class IncompleteCircle with abstract method perimeter
  • Inherit from ABC to 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})")
Circle: area=78.54, perimeter=31.42 Rectangle: area=24.00, perimeter=20.00 Triangle: area=6.00, perimeter=12.00 Largest: Circle (area 78.54)
  • The abstract base guarantees area() and perimeter() exist on every shape.
  • describe() and is_larger_than() are defined once in the base and work for all subclasses.
  • Adding a Pentagon class 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()
Silent motor hum... ElectricCar | Electric | 250 km/h Vroom! PetrolBike | Petrol | 200 km/h

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()
Processing $103.20 USD (fee $3.20) Stripe: charged $103.20 USD Processing $103.49 USD (fee $3.49) PayPal: charged $103.49 USD Processing $102.70 USD (fee $2.70) Square: charged $102.70 USD
  • The abstract base guarantees every gateway has charge(), refund(), and get_transaction_fee().
  • process() is implemented once and works correctly for all gateways — adding SquareGateway required 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

ConceptWhat It DoesKey Syntax
ABCBase class that enables abstract method enforcementclass MyClass(ABC):
@abstractmethodMarks a method every subclass must implement@abstractmethod def m(self): pass
Abstract propertyRequires a property implementation in every subclass@property @abstractmethod
Concrete method in ABCShared logic available to all subclassesRegular method alongside abstract ones
Instantiation guardPrevents creating an incomplete objectRaised as TypeError at instantiation time
Partial implementationSubclass missing abstract methods stays abstractAlso 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?





NEXT UP
Decorators in Python
Wrap functions to add behaviour without changing their code — one of the most powerful and widely used patterns in Python.