RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Python for AI — Zero to Useful
  6. /Ch. 21
Python for AI — Zero to Useful

21. Inheritance and Composition

Chapter 21 of 36 · 15 min
KEY INSIGHT

Inheritance for shared behavior/protocols (like this base wrapper pattern). Composition for capability aggregation (a pipeline that "has-a" tokenizer, "has-a" vectorizer). Most AI pipeline code should lean toward 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.

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.

← Chapter 20
Classes for AI Tools
Chapter 22 →
Regular Expressions