1.4.2 · D1Python & Scientific Computing

Foundations — Functions, classes, and modules

2,777 words13 min readBack to topic

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.

Figure — Functions, classes, and modules
What to look at: the blue card on the left is the stored recipe — Python has only filed it (note the pink "stored, not run"). The yellow arrow is the moment you call 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 linedef compute_mse(y_true, y_pred): — the brackets hold parameters: empty labelled slots the recipe expects to be filled.
  • On a later linecompute_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 f

Why 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 25

Why 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     → RMSE

Why 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 woof

class 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).

Figure — Functions, classes, and modules
What to look at: the yellow box on the left is the single 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 fillings

Why 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_size

Why 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 cabinet

Why 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.

Python basics: variables and indentation

def keyword

colon and indent blocks

parameters and arguments

default values

return

docstring

class blueprint

self this object

__init__ setup

attributes and methods dot

private underscore helpers

import as and modules

Functions classes and modules

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?
Stores the indented block under name without running it — defining is not calling.
Difference between a parameter and an argument?
Parameter is the labelled slot on the definition line; argument is the real value poured in when you call.
What does a function hand back if it has no return?
The empty value None.
What does squared=True in a parameter slot mean?
A default — that slot is optional and becomes True if the caller omits it.
How is a docstring written and does Python run it?
Between triple double-quotes """...""" as the first line inside a def/class; Python stores it but never runs it.
Class vs. object in one line?
Class is the cookie-cutter blueprint; object is one real cookie stamped from it.
What is self inside a method?
A stand-in for the specific object that called the method.
When does __init__ run and who calls it?
Automatically, the instant an object is created; Python calls it, never you by name.
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?
"Internal helper, not part of the public interface" — a convention, not a hard rule.
What are the two unrelated meanings of @?
Matrix multiplication between arrays, and (separately) a decorator above a def.
What does as do in import numpy as np?
Gives the imported module a shorter nickname (np) to use instead of its full name.
What is a module?
A .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.