Skip to content

Abstraction

Hide complexity, expose only what's essential.

Abstraction is the concept of hiding complex implementation details and showing only the essential features of an object. It helps reduce complexity and increase efficiency by allowing users to interact with objects at a higher level without worrying about internal implementation.

Abstraction focuses on what an object does rather than how it does it. It provides a simplified interface to complex systems.

Think of a car:

  • What you see: Steering wheel, pedals, gear shift
  • What’s hidden: Engine mechanics, transmission system, fuel injection

You don’t need to know how the engine works to drive the car - that’s abstraction!

Python provides the abc module to create abstract classes. Abstract classes cannot be instantiated directly and must be subclassed.

abstract_shape.py
from abc import ABC, abstractmethod
class Shape(ABC):
"""Abstract base class - cannot be instantiated"""
@abstractmethod
def area(self):
"""Abstract method - must be implemented by subclasses"""
pass
@abstractmethod
def perimeter(self):
"""Abstract method - must be implemented by subclasses"""
pass
def describe(self):
"""Concrete method - can be used by all subclasses"""
return f"Shape with area {self.area()} and perimeter {self.perimeter()}"
# This will raise TypeError
# shape = Shape() # Can't instantiate abstract class
class Rectangle(Shape):
"""Concrete implementation of Shape"""
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self):
"""Must implement abstract method"""
return self.width * self.height
def perimeter(self):
"""Must implement abstract method"""
return 2 * (self.width + self.height)
class Circle(Shape):
"""Concrete implementation of Shape"""
def __init__(self, radius: float):
self.radius = radius
def area(self):
"""Must implement abstract method"""
import math
return math.pi * self.radius ** 2
def perimeter(self):
"""Must implement abstract method"""
import math
return 2 * math.pi * self.radius
# Now we can create instances
rectangle = Rectangle(5, 3)
print(rectangle.area()) # 15
print(rectangle.describe()) # Uses inherited concrete method
circle = Circle(4)
print(circle.area()) # ~50.27
classDiagram
    class Shape {
        <<abstract>>
        +area()* float
        +perimeter()* float
        +describe() str
    }
    
    class Rectangle {
        -width: float
        -height: float
        +area() float
        +perimeter() float
    }
    
    class Circle {
        -radius: float
        +area() float
        +perimeter() float
    }
    
    Shape <|-- Rectangle
    Shape <|-- Circle
    
    note for Shape "Abstract class\nCannot be instantiated"
payment_abstraction.py
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
"""Abstract base class for payment processing"""
@abstractmethod
def process_payment(self, amount: float) -> bool:
"""Process a payment - must be implemented"""
pass
@abstractmethod
def refund(self, transaction_id: str) -> bool:
"""Process a refund - must be implemented"""
pass
def validate_amount(self, amount: float) -> bool:
"""Concrete method - shared validation logic"""
return amount > 0
class CreditCardProcessor(PaymentProcessor):
"""Concrete implementation for credit card payments"""
def __init__(self, api_key: str):
self.api_key = api_key
def process_payment(self, amount: float) -> bool:
"""Process credit card payment"""
if not self.validate_amount(amount):
return False
# Credit card processing logic
print(f"Processing ${amount} via credit card")
return True
def refund(self, transaction_id: str) -> bool:
"""Process credit card refund"""
print(f"Refunding transaction {transaction_id} via credit card")
return True
class PayPalProcessor(PaymentProcessor):
"""Concrete implementation for PayPal payments"""
def __init__(self, client_id: str, client_secret: str):
self.client_id = client_id
self.client_secret = client_secret
def process_payment(self, amount: float) -> bool:
"""Process PayPal payment"""
if not self.validate_amount(amount):
return False
# PayPal processing logic
print(f"Processing ${amount} via PayPal")
return True
def refund(self, transaction_id: str) -> bool:
"""Process PayPal refund"""
print(f"Refunding transaction {transaction_id} via PayPal")
return True
class CryptoProcessor(PaymentProcessor):
"""Concrete implementation for cryptocurrency payments"""
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def process_payment(self, amount: float) -> bool:
"""Process cryptocurrency payment"""
if not self.validate_amount(amount):
return False
# Crypto processing logic
print(f"Processing ${amount} via cryptocurrency")
return True
def refund(self, transaction_id: str) -> bool:
"""Process cryptocurrency refund"""
print(f"Refunding transaction {transaction_id} via cryptocurrency")
return True
# Usage - abstraction allows treating all processors the same way
def checkout(processor: PaymentProcessor, amount: float):
"""Function works with any PaymentProcessor implementation"""
return processor.process_payment(amount)
# All processors can be used interchangeably
credit_card = CreditCardProcessor("api_key_123")
paypal = PayPalProcessor("client_id", "secret")
crypto = CryptoProcessor("0x1234...")
checkout(credit_card, 100.0) # Works
checkout(paypal, 100.0) # Works
checkout(crypto, 100.0) # Works

You can also define abstract properties:

abstract_properties.py
from abc import ABC, abstractmethod
class Animal(ABC):
"""Abstract base class with abstract properties"""
@property
@abstractmethod
def name(self) -> str:
"""Abstract property - must be implemented"""
pass
@property
@abstractmethod
def sound(self) -> str:
"""Abstract property - must be implemented"""
pass
def make_sound(self):
"""Concrete method using abstract properties"""
return f"{self.name} says {self.sound}"
class Dog(Animal):
def __init__(self, name: str):
self._name = name
@property
def name(self) -> str:
return self._name
@property
def sound(self) -> str:
return "Woof!"
class Cat(Animal):
def __init__(self, name: str):
self._name = name
@property
def name(self) -> str:
return self._name
@property
def sound(self) -> str:
return "Meow!"
dog = Dog("Buddy")
print(dog.make_sound()) # "Buddy says Woof!"
cat = Cat("Whiskers")
print(cat.make_sound()) # "Whiskers says Meow!"
database_abstraction.py
from abc import ABC, abstractmethod
class Database(ABC):
"""Abstract database interface"""
@abstractmethod
def connect(self):
"""Establish database connection"""
pass
@abstractmethod
def execute_query(self, query: str):
"""Execute a database query"""
pass
@abstractmethod
def close(self):
"""Close database connection"""
pass
def __enter__(self):
"""Context manager entry"""
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit"""
self.close()
class PostgreSQLDatabase(Database):
"""PostgreSQL implementation"""
def connect(self):
print("Connecting to PostgreSQL...")
def execute_query(self, query: str):
print(f"Executing PostgreSQL query: {query}")
def close(self):
print("Closing PostgreSQL connection")
class MongoDBDatabase(Database):
"""MongoDB implementation"""
def connect(self):
print("Connecting to MongoDB...")
def execute_query(self, query: str):
print(f"Executing MongoDB query: {query}")
def close(self):
print("Closing MongoDB connection")
# Code works with any database implementation
def run_query(database: Database, query: str):
"""Function works with any Database implementation"""
with database:
database.execute_query(query)
postgres = PostgreSQLDatabase()
mongodb = MongoDBDatabase()
run_query(postgres, "SELECT * FROM users")
run_query(mongodb, 'db.users.find({})')
  • Use abstraction to create flexible, maintainable code that can work with multiple implementations

Abstraction is about creating a contract that all implementations must follow, while hiding the complexity of how each implementation works internally.