Course Content
Python Essentials for AI Engineer
6 sections · 48 lessons
What is Inheritance?
What you need to know
Syntax, overriding and super()
1class Model:2 def __init__(self, name):3 self.name = name4 def predict(self, x):5 raise NotImplementedError6 def describe(self):7 return f"{type(self).__name__}({self.name})"89class ThresholdClassifier(Model):10 def __init__(self, name, threshold=0.5):11 super().__init__(name) # run the parent's setup12 self.threshold = threshold13 def predict(self, x): # override14 return "fraud" if x >= self.threshold else "ok"1516clf = ThresholdClassifier("fraud-v1", 0.8)17print(clf.describe(), clf.predict(0.93)) # ThresholdClassifier(fraud-v1) fraud18print(isinstance(clf, Model)) # True -> a child IS-A parentdescribe is inherited unchanged; predict is replaced. isinstance returns True for parents too, so any code written for Model accepts a ThresholdClassifier.
Abstract base classes
raise NotImplementedError only fails when the method is called. An abstract base class fails earlier, when someone tries to create an incomplete object:
1from abc import ABC, abstractmethod23class BaseModel(ABC):4 @abstractmethod5 def predict(self, x): ...67class Incomplete(BaseModel):8 pass9try:10 Incomplete()11except TypeError as e:12 print(type(e).__name__) # TypeError -> can't instantiate without predict()Multiple inheritance and the MRO
A class can have several parents: class C(A, B). Python flattens the family tree into one ordered list, the MRO, visible as C.__mro__, and searches it left to right. super() means "the next class in the MRO", which is not always the direct parent.
Composition over inheritance
If a Chatbot uses a retriever, it should hold one (self.retriever = retriever), not inherit from Retriever. Composition lets you swap parts at runtime and keeps each class small.
A real-life example
A team calls LLMs from several providers. Retries, timing and logging are the same for all of them; only the HTTP details differ. A base class holds the shared logic, and each subclass fills in one method:
1import time23class LLMClient:4 def complete(self, prompt, retries=2):5 for attempt in range(retries + 1):6 try:7 start = time.perf_counter()8 text = self._send(prompt) # subclass decides how9 ms = (time.perf_counter() - start) * 100010 return f"{text} ({type(self).__name__}, {ms:.0f} ms)"11 except ConnectionError:12 if attempt == retries:13 raise1415 def _send(self, prompt):16 raise NotImplementedError1718class FakeClient(LLMClient): # used in unit tests19 def _send(self, prompt):20 return f"echo: {prompt}"2122print(FakeClient().complete("hello")) # echo: hello (FakeClient, 0 ms)Adding a new provider means writing one _send method; retries and timing come for free. This "template method" pattern is exactly how PyTorch works: nn.Module handles parameters, devices and hooks, and your subclass only overrides forward.
Follow-up questions to expect
- "What is the MRO?" — The method resolution order: the list of classes Python searches, left to right, to find an attribute. See it with
ClassName.__mro__. - "Composition vs inheritance?" — Inheritance models "is-a" and shares an interface; composition models "has-a" and is usually more flexible. Prefer composition unless the "is-a" is real.
- "What does
super()do with multiple inheritance?" — It calls the next class in the MRO, which lets cooperative classes each run once.