Foundations — Functions, classes, and modules
Before we can read a single line of the parent note, we must earn every symbol it throws at us. This page is a dictionary you build by hand: each entry gives the plain meaning, the picture, and the reason the topic needs it. Read top to bottom — each entry uses only things defined above it. This page assumes you have met Python basics.
1. def — the word that names a block of code
The picture. Think of def greet(): as writing a recipe card titled "greet" and slipping it into a card box. Writing the card is not cooking. Cooking happens only when someone later says "make greet" — that is calling the function.

greet(). Only then does the pink box on the right light up and actually print hi. Nothing runs until that arrow fires.
Why the topic needs it. The parent note repeats the same mean-squared-error formula "100 times". def lets you write that recipe card once so every later step just names it.
2. ( ) after a name — the "call" and the "slots"
The round brackets do two different jobs depending on where they sit.
- On the definition line —
def compute_mse(y_true, y_pred):— the brackets hold parameters: empty labelled slots the recipe expects to be filled. - On a later line —
compute_mse(train_labels, predictions):— the same brackets hold arguments: the actual values you pour into those slots.
The picture. A vending machine: the labelled buttons are parameters; the coins you insert are arguments.
Why the topic needs it. In Step 2 of the parent's derivation the function grows (y_true, y_pred). That is exactly the moment a general recipe stops depending on stray global variables and starts accepting whatever data you hand it — the reason functions are reusable at all.
3. : and indentation — how Python marks "inside"
The picture. Indentation is the shape of a bracket you can see. Deeper indent = deeper inside.
def f(x):
y = x + 1 # indented → inside f
return y # still inside f
z = f(3) # back at margin → outside fWhy the topic needs it. Every def, __init__, and method in the parent note relies on this. If you cannot see "inside vs outside" at a glance, class code is unreadable.
4. return — handing a value back out
The picture. The recipe finishes and slides the finished dish out through a window. return is that window.
def square(x):
return x * x
answer = square(5) # answer now holds 25Why the topic needs it. The parent's compute_mse ends in return mse. In Step 4 it even returns different things (mse or mse ** 0.5) depending on a flag — impossible to express without return.
5. Default parameters — param=value
The picture. A recipe card that says "salt: to taste (default: 1 tsp)". Skip it and you still get a sensible dish.
def compute_mse(y_true, y_pred, squared=True):
...
compute_mse(a, b) # squared defaults to True → MSE
compute_mse(a, b, False) # squared given as False → RMSEWhy the topic needs it. This is exactly Step 4 of the parent derivation: one recipe covers MSE and RMSE because the difference is a single optional switch.
6. The docstring — """..."""
Here is exactly how one looks — three double-quotes open it, three close it, and it can span many lines:
def compute_mse(y_true, y_pred):
"""Mean squared error between true and predicted values.
Args:
y_true: list of ground-truth numbers
y_pred: list of predicted numbers
Returns:
one float, the average squared error
"""
return sum((t - p) ** 2 for t, p in zip(y_true, y_pred)) / len(y_true)Everything between the two """ markers is the docstring; the actual work starts at return.
Why the topic needs it. Step 3 of the parent adds one so "future you" knows the expected inputs. It is documentation that travels with the code.
7. class and the object it stamps out
Here is the smallest possible class defined and then instantiated:
class Dog: # the blueprint
def __init__(self, name):
self.name = name # store this dog's own name
def speak(self):
return self.name + " says woof"
rex = Dog("Rex") # make ONE object from the blueprint
buddy = Dog("Buddy") # make ANOTHER, independent object
print(rex.speak()) # Rex says woof
print(buddy.speak()) # Buddy says woofclass Dog: writes the cutter once; Dog("Rex") and Dog("Buddy") stamp out two separate cookies, each remembering its own name.
The picture. One cutter, many cookies. Every cookie has the same shape (attributes and methods) but its own filling (its own weight values).

class DenseLayer blueprint listing the shape every object will have. The two arrows stamp out two objects. The blue box (layer1) and the pink box (layer2) have the identical shape but their own private weight values — that is why the caption says "same shape, separate fillings, self keeps them apart".
Why the topic needs it. The parent builds layer1 and layer2 from the same DenseLayer class. Each owns its private weights, yet both share the forward behaviour — that is the entire point of Attempt-3 winning over the messy dictionary.
8. self — the word for "this particular object"
The picture. When layer1.forward(x) runs, Python quietly slots layer1 into self. So self.weights becomes layer1.weights. Call layer2.forward(x) and now self is layer2.
class Counter:
def __init__(self):
self.count = 0 # this object's own count
def tick(self):
self.count = self.count + 1
a = Counter(); b = Counter()
a.tick(); a.tick(); b.tick()
# a.count == 2 , b.count == 1 — separate fillingsWhy the topic needs it. Without self, layer1.forward() could not know to use layer1's weights. self is the thread tying data to behaviour.
9. __init__ — the setup ceremony
The picture. Unboxing a new appliance: __init__ is the "assembly + set the clock" step that happens before you ever press a button.
class DenseLayer:
def __init__(self, input_size, output_size):
self.input_size = input_size # store into this object
self.output_size = output_sizeWhy the topic needs it. layer1 = DenseLayer(100, 50) sends 100 and 50 straight into __init__, guaranteeing every layer has valid sizes before anyone calls forward.
10. Attributes vs. methods — . the dot
The picture. The object is a labelled drawer. .weights opens the drawer marked weights; .forward() presses the button marked forward.
Why the topic needs it. Every line like x @ self.weights + self.biases and layer1.forward(output) is dot-access. Attributes are nouns; methods are verbs.
11. Leading underscore — _helper
Why the topic needs it. The parent's KNNClassifier exposes fit and (implicitly) predict to users, but keeps _euclidean_distance and _predict_single as internal helpers. The underscore draws that public/private line.
12. @ — matrix multiplication (not classes!)
The picture. x @ W multiplies a row of inputs against a grid of weights and sums — the core of a layer's forward pass y = xW + b.
To make the other @ concrete, a decorator sits on its own line above a def and wraps that function to add extra behaviour:
def shout(func): # a wrapper
def wrapped():
return func().upper()
return wrapped
@shout # <-- decorator form of @
def greet():
return "hi"
print(greet()) # HI (wrapper made it upper-case)Notice this @ never touches arrays — it decorates a function. That is why the two meanings must not be confused.
Why the topic needs it. forward returns x @ self.weights + self.biases. That single line is the layer's job; the array @ carries the actual arithmetic.
13. import, as, and modules — the cabinet
The picture. import is walking to a labelled cabinet and pulling it beside your desk; as np is slapping a short sticky-note name on the front. The dot after np (as in np.zeros) is the same "reach inside" dot from entry 10 — here reaching inside a module instead of an object.
import numpy as np # whole cabinet, nicknamed np
from collections import Counter # take just ONE tool out of a cabinetWhy the topic needs it. The parent's KNN example opens with exactly these two lines. np supplies the array math; Counter tallies which neighbour label wins the vote.
Prerequisite map
The diagram below is read top-to-bottom: every box feeds into the box it points to, and all roads eventually arrive at the topic itself. Trace one path in words: Python basics teaches you indentation, which lets you understand the class blueprint, which needs self and __init__, which together let you read attributes and methods, which — combined with functions and imports — is everything the parent topic assumes. If you can walk every arrow and explain the box it lands on, you are ready.
Everything on the left feeds the topic on the right. Master the left column and the parent note reads like plain English.
Equipment checklist
Cover the right side and test yourself.
What does def name(): actually do the moment Python reads it?
name without running it — defining is not calling.Difference between a parameter and an argument?
What does a function hand back if it has no return?
None.What does squared=True in a parameter slot mean?
True if the caller omits it.How is a docstring written and does Python run it?
"""...""" as the first line inside a def/class; Python stores it but never runs it.Class vs. object in one line?
What is self inside a method?
When does __init__ run and who calls it?
What is the difference between layer1.weights and layer1.forward()?
.weights fetches a stored value (attribute); .forward() runs a stored action (method), hence brackets.What does a leading underscore like _helper signal?
What are the two unrelated meanings of @?
def.What does as do in import numpy as np?
np) to use instead of its full name.What is a module?
.py file containing functions and classes.Next: with the vocabulary earned, revisit the parent topic and see the Object-oriented programming in ML and Building neural networks from scratch pages where these pieces assemble into real models.