Abstract Base Classes define interfaces that subclasses must implement. They enforce contracts at instantiation time, catching errors early.

Basic Abstract Class

from abc import ABC, abstractmethod
 
class Animal(ABC):
    @abstractmethod
    def speak(self) -> str:
        """All animals must implement speak."""
        pass
    
    @abstractmethod
    def move(self) -> str:
        """All animals must implement move."""
        pass
 
# Cannot instantiate abstract class
# animal = Animal()  # TypeError!
 
class Dog(Animal):
    def speak(self) -> str:
        return "Woof!"
    
    def move(self) -> str:
        return "Running on four legs"
 
dog = Dog()  # OK
print(dog.speak())  # Woof!

Abstract Properties

from abc import ABC, abstractmethod
 
class Shape(ABC):
    @property
    @abstractmethod
    def area(self) -> float:
        pass
    
    @property
    @abstractmethod
    def perimeter(self) -> float:
        pass
 
class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height
    
    @property
    def area(self) -> float:
        return self.width * self.height
    
    @property
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)
 
rect = Rectangle(10, 5)
print(rect.area)       # 50
print(rect.perimeter)  # 30

Concrete Methods in ABCs

from abc import ABC, abstractmethod
 
class Database(ABC):
    @abstractmethod
    def connect(self) -> None:
        pass
    
    @abstractmethod
    def execute(self, query: str) -> list:
        pass
    
    # Concrete method - shared implementation
    def query(self, sql: str) -> list:
        self.connect()
        return self.execute(sql)
 
class PostgreSQL(Database):
    def connect(self) -> None:
        print("Connecting to PostgreSQL")
    
    def execute(self, query: str) -> list:
        print(f"Executing: {query}")
        return []
 
db = PostgreSQL()
db.query("SELECT * FROM users")

Abstract Class Methods

from abc import ABC, abstractmethod
 
class Serializer(ABC):
    @classmethod
    @abstractmethod
    def from_string(cls, data: str) -> 'Serializer':
        pass
    
    @abstractmethod
    def to_string(self) -> str:
        pass
 
class JSONSerializer(Serializer):
    def __init__(self, data: dict):
        self.data = data
    
    @classmethod
    def from_string(cls, data: str) -> 'JSONSerializer':
        import json
        return cls(json.loads(data))
    
    def to_string(self) -> str:
        import json
        return json.dumps(self.data)

Abstract Static Methods

from abc import ABC, abstractmethod
 
class Validator(ABC):
    @staticmethod
    @abstractmethod
    def validate(value: str) -> bool:
        pass
 
class EmailValidator(Validator):
    @staticmethod
    def validate(value: str) -> bool:
        return "@" in value and "." in value
 
class PhoneValidator(Validator):
    @staticmethod
    def validate(value: str) -> bool:
        return value.isdigit() and len(value) >= 10

Register Virtual Subclasses

from abc import ABC, abstractmethod
 
class Drawable(ABC):
    @abstractmethod
    def draw(self) -> None:
        pass
 
# Third-party class you can't modify
class ExternalWidget:
    def draw(self) -> None:
        print("Drawing widget")
 
# Register as virtual subclass
Drawable.register(ExternalWidget)
 
widget = ExternalWidget()
print(isinstance(widget, Drawable))  # True

Using subclasshook

from abc import ABC, abstractmethod
 
class Sized(ABC):
    @abstractmethod
    def __len__(self) -> int:
        pass
    
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if hasattr(C, '__len__'):
                return True
        return NotImplemented
 
# Any class with __len__ is considered Sized
class MyContainer:
    def __len__(self):
        return 10
 
print(isinstance(MyContainer(), Sized))  # True

Plugin Architecture

from abc import ABC, abstractmethod
from typing import Dict, Type
 
class Plugin(ABC):
    @property
    @abstractmethod
    def name(self) -> str:
        pass
    
    @abstractmethod
    def execute(self, data: dict) -> dict:
        pass
 
class PluginManager:
    _plugins: Dict[str, Type[Plugin]] = {}
    
    @classmethod
    def register(cls, plugin_class: Type[Plugin]):
        instance = plugin_class()
        cls._plugins[instance.name] = plugin_class
        return plugin_class
    
    @classmethod
    def get(cls, name: str) -> Plugin:
        return cls._plugins[name]()
 
@PluginManager.register
class UppercasePlugin(Plugin):
    @property
    def name(self) -> str:
        return "uppercase"
    
    def execute(self, data: dict) -> dict:
        return {k: v.upper() if isinstance(v, str) else v 
                for k, v in data.items()}
 
plugin = PluginManager.get("uppercase")
print(plugin.execute({"name": "alice"}))  # {"name": "ALICE"}

Repository Pattern

from abc import ABC, abstractmethod
from typing import Generic, TypeVar, List, Optional
 
T = TypeVar('T')
 
class Repository(ABC, Generic[T]):
    @abstractmethod
    def get(self, id: int) -> Optional[T]:
        pass
    
    @abstractmethod
    def get_all(self) -> List[T]:
        pass
    
    @abstractmethod
    def save(self, entity: T) -> T:
        pass
    
    @abstractmethod
    def delete(self, id: int) -> bool:
        pass
 
class User:
    def __init__(self, id: int, name: str):
        self.id = id
        self.name = name
 
class InMemoryUserRepository(Repository[User]):
    def __init__(self):
        self._users: dict[int, User] = {}
        self._next_id = 1
    
    def get(self, id: int) -> Optional[User]:
        return self._users.get(id)
    
    def get_all(self) -> List[User]:
        return list(self._users.values())
    
    def save(self, user: User) -> User:
        if user.id is None:
            user.id = self._next_id
            self._next_id += 1
        self._users[user.id] = user
        return user
    
    def delete(self, id: int) -> bool:
        if id in self._users:
            del self._users[id]
            return True
        return False

Strategy Pattern

from abc import ABC, abstractmethod
 
class CompressionStrategy(ABC):
    @abstractmethod
    def compress(self, data: bytes) -> bytes:
        pass
    
    @abstractmethod
    def decompress(self, data: bytes) -> bytes:
        pass
 
class GzipStrategy(CompressionStrategy):
    def compress(self, data: bytes) -> bytes:
        import gzip
        return gzip.compress(data)
    
    def decompress(self, data: bytes) -> bytes:
        import gzip
        return gzip.decompress(data)
 
class NoCompressionStrategy(CompressionStrategy):
    def compress(self, data: bytes) -> bytes:
        return data
    
    def decompress(self, data: bytes) -> bytes:
        return data
 
class DataProcessor:
    def __init__(self, strategy: CompressionStrategy):
        self.strategy = strategy
    
    def process(self, data: bytes) -> bytes:
        compressed = self.strategy.compress(data)
        # ... do something ...
        return self.strategy.decompress(compressed)

Checking Implementation

from abc import ABC, abstractmethod
 
class Handler(ABC):
    @abstractmethod
    def handle(self, request: dict) -> dict:
        pass
 
def is_handler(obj) -> bool:
    """Check if object implements Handler interface."""
    return isinstance(obj, Handler)
 
def get_missing_methods(cls, abc_class) -> list:
    """Get list of abstract methods not implemented."""
    return list(getattr(abc_class, '__abstractmethods__', set()))
 
# Check what needs implementing
print(Handler.__abstractmethods__)  # {'handle'}

Best Practices

from abc import ABC, abstractmethod
 
# 1. Keep interfaces small
class Readable(ABC):
    @abstractmethod
    def read(self) -> bytes:
        pass
 
class Writable(ABC):
    @abstractmethod
    def write(self, data: bytes) -> None:
        pass
 
# 2. Compose interfaces
class ReadWritable(Readable, Writable):
    pass
 
# 3. Document expected behavior
class Cache(ABC):
    @abstractmethod
    def get(self, key: str) -> str | None:
        """Return cached value or None if not found."""
        pass
    
    @abstractmethod
    def set(self, key: str, value: str, ttl: int = 3600) -> None:
        """Store value with optional TTL in seconds."""
        pass

ABCs enforce interface contracts, making code more maintainable and self-documenting. Use them to define clear boundaries in your architecture.

React to this post: