Why should a machine-learning book care? Because a trained model is an object — some learned numbers plus a predict function that uses them. By the end of this chapter you will have invented the fit/predict interface that scikit-learn, Keras and friends all use, hand-built a decision tree out of objects, and saved a model to disk.
You could describe a customer with a dict: customer = {"name": "Sandeep", "place": "India"}. It works, but the data has no behavior: any code that wants to do something with a customer must live somewhere else.
Write a class Customer:
Customer("Sandeep", "India") stores the two values on the object (hint: define __init__(self, name, place) and assign to self.name, self.place).describe() returns "<name> from <place>".What just happened?
Customer("Sandeep", "India") constructs an object: Python creates an empty object and calls your __init__ with it as self.c.describe() is exactly Customer.describe(c) — the object slides in as self. Try it: Customer.describe(c) returns the same string.c and c2 each carry their own name and place. One class, many objects.Remember the thermometer from Expressions & Functions? Calibration data: current 1 → 30°, current 2 → 35°. What temperature is current 3? You solved it with two loose functions, fit and predict, and had to carry the (m, c) tuple between them yourself. Let the object carry it.
Write a class TwoPointLine with:
fit(x, y) — x and y are lists of two values each: two points (x[0], y[0]) and (x[1], y[1]). Compute the slope m = (y2 - y1) / (x2 - x1) and intercept c = y2 - m * x2, and store them on self.predict(xs) — take a list of x values, return the list of m * x + c.model = TwoPointLine()
model.fit([2, 5], [3, 9])
model.predict([3]) # [5.0]
You just invented the scikit-learn API
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X, y)
model.predict(X_new)
Same shape: construct → fit stores learned parameters on the object → predict uses them. Every model in the rest of this book fits this mold; the only thing that changes is how fit finds the parameters.
fit silently misbehaves if you hand it three points. Make it fail loudly instead: copy your class below and make fit raise a ValueError unless both lists have exactly two values (hint: raise ValueError("need exactly two points")).
All animals eat; only dogs bark. Instead of repeating eat in every animal class, write it once and inherit it.
Animal has a method eat() returning "eating".Dog(Animal) adds bark() returning "woof!" — and must not redefine eat.Cat(Animal) adds meow() returning "meow".The class in parentheses — class Dog(Animal): — is the parent. Everything the parent can do, the child can do too.
In the Animal/Dog/Cat example, Dog inherits from Animal. What is the main benefit of this inheritance?
A. Dogs run faster in Python than standalone classes
B. Shared behavior (like `speak` or `name`) is defined once in `Animal`, and each subclass only adds or overrides what's different
C. Python requires all classes to inherit from something
D. It prevents you from creating `Animal` objects directly
An object's attributes can be other objects — that is how you build trees. Here is a company's expense tree: each node is a department with its own expense, and (up to) two sub-departments:
10
/ \
4 5
/ \ / \
1 11 15 16
Write a class Node:
Node(val, left=None, right=None) stores the value and the two children.total() returns this node's val plus the totals of its children (skip a child if it is None).count() returns how many nodes are in this subtree.Notice the recursion: a method calling the same method on its children.
Extend your Node class (copy it and add methods):
min_val() / max_val() — smallest / largest value in the subtree.find(x) — True if x appears anywhere in the subtree.max_depth() — length of the longest root-to-leaf path (a lone node has depth 1). Careful: the naive max(self.left.max_depth(), self.right.max_depth()) + 1 crashes on a leaf — handle None children.apply(func) — replace every node's val with func(val), in place.You get a job offer. Do you take it? Your (very rational) rules:
That is a tree of decisions — and you can build it from objects.
DecisionNode(criteria, boundary, left, right) stores all four. Its method answer(circumstance) looks up circumstance[self.criteria] (circumstance is a dict) and delegates: How should the node decide which child to ask? What role does the boundary play?Yes(DecisionNode) and No(DecisionNode) are leaf classes: their answer ignores the circumstance and returns True / False. Give them their own no-argument __init__.No if-chains about salaries anywhere — the structure of the objects encodes the rules.
This is a real decision tree
You built its structure by hand. The Decision Trees chapter later in this book answers the natural next question: given a table of past decisions, how can fit discover the right criteria and boundaries automatically? Same object, learned instead of hand-crafted.
You built a decision tree using DecisionNode, Yes, and No classes. Could you have represented the same tree using nested dictionaries instead? What would you lose?
A. Dictionaries can't be nested, so it's impossible
B. It would work, but you'd lose the ability to call methods like `.predict()` directly on the tree — you'd need separate functions that know the dict structure
C. Dictionaries are faster, so you'd actually gain performance
D. You'd lose nothing — classes and dicts are interchangeable
Your program built a useful tree — then it exits, and the tree is gone. A model you cannot save is a model you must retrain. Let's serialize it.
JSON can store dicts, lists, numbers, strings and booleans — but not your objects. So the plan is: object → dict → JSON file, and back.
to_dict(node) — a Yes leaf becomes {"leaf": True}, a No leaf becomes {"leaf": False}, and an inner node becomes {"criteria": ..., "boundary": ..., "left": <dict>, "right": <dict>} (children converted recursively). Hint: isinstance(node, Yes) tells leaves apart.from_dict(d) — the exact inverse, rebuilding real objects.The test saves the offer tree to a real file, loads it back, and checks that the reloaded tree gives the same answers.
Aside — pickle
Python's pickle module can serialize almost any object in one line (pickle.dump(offer_tree, f)), no to_dict needed. So why bother? A pickle file is Python-only, unreadable by humans, and unsafe to load from untrusted sources. Your JSON file is none of those things. Real ML tooling makes the same trade-off: scikit-learn models ship as pickles (via joblib), while model configs travel as JSON or YAML.
TwoPointLine fits a line through 2 points. In 3D, three points determine a plane z = a*x + b*y + d — and finding a, b, d means solving three linear equations.
TwoPointLine fits a line through 2 points. In 3D, three points determine a plane z = a*x + b*y + d — and finding a, b, d means solving three linear equations:
x1*a + y1*b + 1*d = z1
x2*a + y2*b + 1*d = z2
x3*a + y3*b + 1*d = z3
You already own a tool for that: paste your solve_equations (and its helpers) from the Loops and Arrays chapter into the cell, then write FitPlane:
fit(points) — points is a list of three (x, y, z) tuples. Build the three equations [x, y, 1, z] and solve for (a, b, d).predict(points) — a list of (x, y) tuples in, a list of a*x + b*y + d out.| Part | You built | The idea |
|---|---|---|
| 1 | Customer |
__init__, self, methods — data + behavior in one place |
| 2 | TwoPointLine |
the fit/predict model API, input validation |
| 3 | Animal → Dog, Cat |
inheritance |
| 4 | Node.total/count/min_val/max_val/find/max_depth/apply |
recursion on object trees |
| 5 | DecisionNode, Yes, No |
a decision tree as objects; leaves via inheritance |
| 6 | to_dict / from_dict |
serialization — models that survive the program |
| Bonus | FitPlane |
fit = solving equations (three points → a plane) |
From here on, everything is objects: the Ancient Secrets of Prediction chapter finds best-fit models when no exact fit exists, Gradient Descent makes fit iterative, and Decision Trees learns Part 5's tree from data.
Source on GitHub · Back to all chapters
© 2026 CloudxLab. All rights reserved.