21. Inheritance and Composition
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.
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.