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) # 30Concrete 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) >= 10Register 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)) # TrueUsing 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)) # TruePlugin 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 FalseStrategy 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."""
passABCs enforce interface contracts, making code more maintainable and self-documenting. Use them to define clear boundaries in your architecture.
React to this post: