21. Inheritance and Composition

Chapter 21 of 36 · 15 min

There's a war over what's "correct" in Python OOP: inheritance versus composition. For AI tools, composition usually wins, but inheritance has legitimate uses.

Inheritance works when you have a genuine "is-a" relationship and want to inherit behavior wholesale:

class BaseModelWrapper:
    def __init__(self, model_name: str):
        self.model_name = model_name
        self._ready = False
    
    def load(self):
        print(f"Loading {self.model_name}...")
        self._ready = True
    
    def predict(self, input_data):
        if not self._ready:
            raise RuntimeError("Model not loaded. Call load() first.")
        return self._do_predict(input_data)
    
    def _do_predict(self, input_data):
        raise NotImplementedError("Subclass must implement")

class TextClassifier(BaseModelWrapper):
    def __init__(self, model_name: str, num_classes: int):
        super().__init__(model_name)
        self.num_classes = num_classes
    
    def _do_predict(self, input_data):
        return {"class": "positive", "confidence": 0.87}

class SentimentAnalyzer(BaseModelWrapper):
    def _do_predict(self, input_data):
        return {"sentiment": "positive", "score": 0.92}

The super().__init__() call chains to the parent class. Template methods (_do_predict) let you define the family of behaviors centrally while allowing variation.

But when behavior sharing isn't the goal, compose instead:

class TokenCounter:
    def count(self, text: str) -> int:
        return len(text.split())

class WordFrequencyAnalyzer:
    def __init__(self):
        self.counter = TokenCounter()  # Composition, not inheritance
    
    def analyze(self, text: str) -> dict:
        tokens = text.lower().split()
        freq = {}
        for token in tokens:
            freq[token] = freq.get(token, 0) + 1
        return freq

Local verification checkpoint

Run the smallest example from this chapter in a local workspace and record the package version, runtime, data path, and observed output. If the result depends on model size, vector count, CPU/GPU backend, or available memory, note that constraint beside the exercise so the lesson remains reproducible.

Local verification checkpoint

Run the smallest example from this chapter in a local workspace and record the package version, runtime, data path, and observed output. If the result depends on model size, vector count, CPU/GPU backend, or available memory, note that constraint beside the exercise so the lesson remains reproducible.

EXERCISE

Create a BaseEmbedder class with load() and embed(text) methods. Subclass it with OpenAIEmbedder and LocalEmbedder. Now create a RetrievalPipeline class that composes (not inherits) an embedder and a VectorStore object. Show how you can swap embedders without changing the pipeline code.