← Master Index
Vol. 03 Module 3.2 Lecture

Inheritance

Object-Oriented Programming

How This Lesson Fits the Module

You can define a class from scratch every time, but ML code repeats patterns: every dataset has __len__ and __getitem__; every PyTorch model extends nn.Module; every scikit-learn classifier implements fit and predict.

Inheritance lets a child class reuse and extend a parent class. You write only what is different—custom layers, domain-specific preprocessing, specialized loss logging—while inheriting the rest.

Learning Objectives

By the end of this lesson, students should be able to:

  • Define a subclass that inherits from a parent class using class Child(Parent).
  • Override parent methods while calling the parent implementation with super().
  • Explain how nn.Module inheritance works in PyTorch custom models.
  • Extend a base dataset class with domain-specific loading logic.
  • Distinguish useful inheritance from deep, fragile class hierarchies.
  • Recognize inheritance in scikit-learn’s estimator hierarchy.

Basic Inheritance Syntax

Definition — Inheritance

Inheritance creates an is-a relationship: a child class (subclass) acquires attributes and methods from a parent class (superclass). The child may override methods to specialize behavior while reusing common structure.

class BaseDataset:
    def __init__(self, transform=None):
        self.transform = transform

    def __len__(self):
        raise NotImplementedError("Subclass must implement __len__")

    def __getitem__(self, index):
        raise NotImplementedError("Subclass must implement __getitem__")


class CSVDataset(BaseDataset):
    """Dataset backed by a CSV file — inherits transform handling."""

    def __init__(self, csv_path, transform=None):
        super().__init__(transform)   # call parent constructor
        self.rows = self._load_csv(csv_path)

    def _load_csv(self, path):
        # simplified: list of (features, label) tuples
        return [([1.0, 2.0], 0), ([3.0, 4.0], 1)]

    def __len__(self):
        return len(self.rows)

    def __getitem__(self, index):
        features, label = self.rows[index]
        if self.transform:
            features = self.transform(features)
        return features, label

CSVDataset is a BaseDataset. It inherits transform handling from the parent and implements the abstract indexing contract.

super() and Method Overriding

When a child overrides a method, super() calls the parent version. This is critical in PyTorch where nn.Module.__init__ must run to register sub-layers:

import torch.nn as nn

class MLPClassifier(nn.Module):
    def __init__(self, input_dim, hidden_dim, num_classes):
        super().__init__()                    # REQUIRED: registers parameters
        self.encoder = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
        )
        self.head = nn.Linear(hidden_dim, num_classes)

    def forward(self, x):
        features = self.encoder(x)
        return self.head(features)

Without super().__init__(), PyTorch cannot track encoder and head parameters—model.parameters() would be empty and training would fail silently (no learnable weights).

ML Pattern: Extending torch.utils.data.Dataset

from torch.utils.data import Dataset
from PIL import Image
import os

class ImageFolderDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = root_dir
        self.transform = transform
        self.image_paths = sorted(os.listdir(root_dir))

    def __len__(self):
        return len(self.image_paths)

    def __getitem__(self, index):
        path = os.path.join(self.root_dir, self.image_paths[index])
        image = Image.open(path).convert("RGB")
        label = self._infer_label(path)
        if self.transform:
            image = self.transform(image)
        return image, label

    def _infer_label(self, path):
        return 0 if "cat" in path else 1

By inheriting Dataset, your class is compatible with DataLoader, distributed samplers, and every PyTorch utility that expects the standard interface.

Inheritance in scikit-learn

scikit-learn estimators inherit from BaseEstimator and mix in interfaces like ClassifierMixin:

Parent / Mixin What Child Classes Inherit
BaseEstimator get_params, set_params for hyperparameter grids and pipelines
ClassifierMixin score method using accuracy for classifiers
RegressorMixin score method using R² for regressors

When you write GridSearchCV(LogisticRegression()), inheritance is why every estimator exposes the same fit/predict API regardless of internal algorithm.

When to Inherit vs. Compose

Inherit

Child is a specialized version of parent. ResNet is an nn.Module. CSVDataset is a Dataset.

Compose

Object has a component. MLPClassifier has an encoder Sequential—not subclassing each layer.

ML Guidance

Inherit framework base classes; compose layers, transforms, and utilities. Avoid deep custom hierarchies beyond 2–3 levels.

Engineering Principle

Prefer composition over deep inheritance for application logic. In ML frameworks, shallow inheritance (subclass nn.Module or Dataset) plus composition (stack layers, chain transforms) scales better than ten-level class trees.

Method Resolution Order (MRO)

Python searches for methods from child to parent along the Method Resolution Order. For single inheritance (one parent), this is straightforward: child method first, then parent.

Multiple inheritance exists but is rare in ML application code. Stick to single parent (nn.Module, Dataset, BaseEstimator) unless a framework requires mixins.

Common Misconceptions

Misconception 1: “Inheritance copies parent code into the child.”

Reality: The child references parent methods at runtime. Overrides replace specific methods; unoverridden ones resolve via MRO.

Misconception 2: “Skipping super().__init__() is fine if I define my own layers.”

Reality: Framework base classes perform essential setup. In PyTorch, skipped super().__init__() breaks parameter registration.

Quick Knowledge Check

  1. Short Answer: What does super().__init__() do in a PyTorch model? Answer: Calls nn.Module’s constructor to register submodules and parameters.
  2. True/False: CSVDataset inheriting BaseDataset means CSV loading is copied into every subclass file. Answer: False—inheritance shares behavior via lookup, not duplication.
  3. Short Answer: Give an example of composition in a neural network class. Answer: e.g., self.encoder = nn.Sequential(...) inside MLPClassifier.
  4. Short Answer: What relationship does inheritance model? Answer: An is-a relationship: the subclass extends the superclass.
  5. True/False: Skipping super().__init__() in a PyTorch nn.Module subclass is usually harmless. Answer: False—it breaks parameter registration.
  6. Multiple Choice: Method lookup in single inheritance follows: (a) parent then child, (b) child then parent (MRO), (c) random, (d) only globals. Answer: (b).
  7. Short Answer: Why prefer shallow inheritance plus composition in ML code? Answer: Deep hierarchies are fragile; stacking layers/transforms scales better.
  8. True/False: Multiple inheritance is common and recommended in everyday ML application code. Answer: False—stick to a single parent unless mixins are required.
  9. Short Answer: How does scikit-learn get a uniform estimator API? Answer: Shared base classes and mixins such as BaseEstimator.
  10. Multiple Choice: class CSVDataset(BaseDataset) means CSVDataset: (a) copies all parent source into its file, (b) is-a BaseDataset and reuses behavior via lookup, (c) cannot override methods, (d) must not call super(). Answer: (b).

Key Takeaways

  • Inheritance models is-a relationships; subclasses extend superclass behavior.
  • super() invokes parent methods—critical for nn.Module.__init__ and shared setup.
  • Custom PyTorch datasets and models inherit framework base classes for ecosystem compatibility.
  • scikit-learn’s uniform API comes from shared base classes and mixins.
  • Prefer shallow inheritance plus composition for maintainable ML code.
Trainer Guide

Hands-on: Have students subclass nn.Module twice—once with super().__init__() and once without. Compare list(model.parameters()) so the registration failure is visible.

Discussion: When would you inherit Dataset versus compose a loader helper inside a plain class?

What’s Next Continue to Polymorphism to see how different model and dataset objects can be used interchangeably through shared interfaces.