← Master Index
Vol. 03 Module 3.2 Lecture

Objects

Object-Oriented Programming

How This Lesson Fits the Module

Classes defined the blueprint. An object is what you actually create and use at runtime—the live dataset in memory, the model holding trained weights, the optimizer tracking momentum buffers.

ML pipelines are chains of objects: a DataLoader yields batch objects (tensors), a model object transforms them, a loss object scores predictions. Understanding object state, identity, and lifetime prevents subtle bugs in training scripts.

Learning Objectives

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

  • Instantiate objects from a class and access their attributes and methods.
  • Explain object identity (is) versus equality (==) in Python.
  • Describe how object state changes during ML training (weights update, metrics accumulate).
  • Trace how objects are passed by reference and why mutating shared state causes bugs.
  • Wire together dataset, model, and optimizer objects in a minimal training step.
  • Recognize when to create a new object versus modify an existing one.

Creating and Using Objects

Instantiation calls the class like a function. Python allocates memory, runs __init__, and returns a new object:

config = ModelConfig(learning_rate=0.001, batch_size=64, epochs=20)
print(config.summary())
# lr=0.001, batch=64, epochs=20

The variable config holds a reference to the object—not a copy of the class definition. You interact with the object through dot notation: config.learning_rate, config.summary().

Definition — Object State

An object’s state is the collective value of its instance attributes at a given moment. A neural network’s state is its weight tensors; a metrics tracker’s state is its running loss and accuracy counts. Training changes object state over time.

Objects in an ML Training Step

Consider a simplified training step with three cooperating objects:

import torch
import torch.nn as nn

# Three objects, each with its own state
model = nn.Linear(4, 1)          # weights, bias
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
criterion = nn.MSELoss()

# One batch: tensors are objects too
inputs = torch.randn(8, 4)
targets = torch.randn(8, 1)

# State flows through method calls
predictions = model(inputs)       # model.forward()
loss = criterion(predictions, targets)
optimizer.zero_grad()
loss.backward()                   # gradients stored on parameter objects
optimizer.step()                  # mutates model weight state
inputs, targets — Tensor objects holding batch data model(inputs) — Model object reads its weight state, returns predictions criterion(...) — Loss object computes scalar from predictions and targets loss.backward() — Gradient objects attached to parameter tensors optimizer.step() — Optimizer mutates model parameter state

Each object has a role. None of them is a loose global variable—they are composed in a pipeline.

Identity vs. Equality

Operator Question It Answers ML Example
is Are these the same object in memory? optimizer.param_groups[0]['params'][0] is model.weight — True (same tensor object)
== Do these objects have equal values? Two models with identical weights may be == but not is the same object
a = TabularDataset([[1]], [0])
b = a          # b references the same object as a
c = TabularDataset([[1]], [0])  # new object, equal data

print(a is b)    # True  — same object
print(a is c)    # False — different objects
print(a[0] == c[0])  # True — same values

Mutability and Shared State

Most ML objects are mutable—their state can change after creation. This is powerful (training updates weights) but dangerous when objects are unintentionally shared.

Bug Pattern — Shared List
history = []

class MetricTracker:
    def __init__(self, name, log=history):  # DANGER: shared default
        self.name = name
        self.log = log

train_metrics = MetricTracker("train")
val_metrics = MetricTracker("val")
train_metrics.log.append(0.9)
print(val_metrics.log)  # [0.9] — val polluted by train!

Fix: Use None as default and create a fresh list inside __init__. Each object should own its state.

The same principle applies to datasets: if two dataset objects share a mutable list of features and one applies in-place augmentation, both see the change. Clone or copy when isolation is required.

Object Lifetime in a Pipeline

Creation

dataset = ImageDataset(...) — Load paths, set transforms. Heavy I/O may be lazy.

Active Use

Training loop reads and mutates objects: batches drawn, weights updated, logs appended.

Persistence

torch.save(model.state_dict(), ...) serializes state. New model object can reload weights.

# Save and restore model state (new object, same weights)
torch.save(model.state_dict(), "checkpoint.pt")

model_v2 = nn.Linear(4, 1)           # fresh object
model_v2.load_state_dict(torch.load("checkpoint.pt"))

Representing Objects as Strings

Implement __repr__ for debugging—essential when logging dozens of experiment objects:

class TabularDataset:
    # ... previous methods ...

    def __repr__(self):
        return f"TabularDataset(n={len(self)}, features={self.num_features()})"

print(train_ds)  # TabularDataset(n=1000, features=12)

Common Misconceptions

Misconception 1: “Assigning b = a copies the object.”

Reality: It copies the reference. Both names point to the same object. Use copy.copy() or .clone() for tensors when you need independence.

Misconception 2: “Functions receive copies of objects.”

Reality: Python passes object references. If a function mutates list.append or tensor.add_(), the caller sees the change.

Quick Knowledge Check

  1. Short Answer: What changes when optimizer.step() runs? Answer: Model parameter (weight/bias) values—the model object’s state.
  2. True/False: a is b implies a == b for all objects. Answer: False in general (e.g., NaN), but True for identical references with consistent equality.
  3. Short Answer: Why implement __repr__ on custom dataset classes? Answer: Clear debugging output when printing or logging objects.
  4. Multiple Choice: Two models with identical weights after training are always the same object. (a) True (b) False. Answer: (b) False.
  5. Short Answer: What is an object’s state? Answer: The collective value of its instance attributes at a given moment.
  6. True/False: b = a copies the object so later mutations to b leave a unchanged. Answer: False—it copies the reference; both names point to the same object.
  7. Short Answer: How should you get an independent tensor copy when needed? Answer: Use copy.copy() or tensor .clone().
  8. Multiple Choice: Checkpoints typically: (a) preserve object identity forever, (b) save and restore state without keeping the same identity, (c) delete the class, (d) freeze Python itself. Answer: (b).
  9. True/False: Python functions receive copies of objects, so list.append inside a function cannot affect the caller. Answer: False—Python passes references; mutations are visible to the caller.
  10. Short Answer: Name three cooperating objects in a minimal training step. Answer: Model, optimizer, and loss/criterion (plus tensor batches).

Key Takeaways

  • Objects are live instances with mutable state (attributes) and behavior (methods).
  • ML pipelines compose objects: datasets, models, losses, optimizers, tensors.
  • is checks identity; == checks value equality—different questions.
  • Shared mutable state causes cross-contamination; each object should own its data when possible.
  • Checkpoints save and restore object state without preserving object identity.
Trainer Guide

Hands-on: Assign b = a on a shared list or tensor, mutate b, and print a. Then repeat with .clone(). Students should feel identity versus copy before talking about checkpoints.

Discussion: After loading a checkpoint, is the restored model the same object as before training? Why does that distinction matter for logging and serving?

What’s Next Continue to Inheritance to reuse and extend class definitions—the pattern behind nn.Module, custom datasets, and estimator hierarchies.