Chapter 1: Python for People Who Already Code

The language features ML code leans on, for readers who already program in another language.

Loops, functions and types work in Python much as they do in any language you already know. ML code adds a handful of Python habits it uses on every page, plus one piece of setup: a per project. This chapter covers both, fast, by contrast with other languages.

A is like a Go slice or a JavaScript array: ordered, growable, repeats allowed. A maps keys to values, like a map or hash table elsewhere, and a keeps each value once. A builds any of the three in one expression, with the loop, the filter and the append folded together.

# a list keeps order and repeats
runs = ['a', 'b', 'a']
runs.append('d')
runs
Output:
['a', 'b', 'a', 'd']
# a dict maps keys to values
lr = {'a': 0.1, 'b': 0.05}
lr['c'] = 0.01
lr
Output:
{'a': 0.1, 'b': 0.05, 'c': 0.01}
# a set keeps each value once, in no guaranteed order
{3, 1, 3, 2}
Output:
{1, 2, 3}
[x * x for x in range(6) if x % 2 == 0]
Output:
[0, 4, 16]

One small job puts several of these to work: reading a CSV of training runs by hand. names the file, splits the header from the other lines, and a turns each line into a .

CSV Example — pathlib.Path Step 1 of 9
from pathlib import Pathpath = Path('data') / 'runs.csv'text = path.read_text()header, *lines = text.splitlines()columns = header.split(',')rows = [dict(zip(columns, line.split(','))) for line in lines]rows[0]['loss'] + rows[1]['loss']losses = {row['run']: float(row['loss']) for row in rows}import pandas as pddf = pd.read_csv(path)df['loss'][0] + df['loss'][1]

path

PosixPath('data/runs.csv')

The / operator joins path parts. On Windows the same line makes a WindowsPath.

Every value came back a string, which is why step six joins two losses as text instead of adding them, with no error. pandas, in Chapter 4, reads a file like it into typed columns in one call.

An puts expressions inside a string, with an optional format after a colon: {loss:.2f} prints two decimals. In a function’s parameters, collects extra positional arguments into a tuple and collects extra keyword arguments into a . At a call the stars work the other way round, so train(**config) feeds a config straight into a function.

loss = 0.4213
f'loss {loss:.2f} after {3 * 2} epochs'
Output:
'loss 0.42 after 6 epochs'
def log(*values, **fields):
    return values, fields
log(1, 2, lr=0.1)
Output:
((1, 2), {'lr': 0.1})
def train(lr, epochs):
    return f'lr={lr}, epochs={epochs}'
config = {'lr': 0.1, 'epochs': 5}
train(**config)
Output:
'lr=0.1, epochs=5'

A is a struct, as in Go or C, with its constructor, printing and equality written for you. Its fields carry , and here Python parts ways with statically typed languages: no compiler checks them, and Python doesn’t either when the code runs. This is the easy one to get wrong. Run('b', '0.05') builds without complaint, and the string shows up later: as a TypeError if you’re lucky, or, as below, as a wrong answer with no error.

from dataclasses import dataclass
@dataclass
class Run:
    name: str
    lr: float
Run('a', 0.1)
Output:
Run(name='a', lr=0.1)
Run('a', 0.1) == Run('a', 0.1)
Output:
True
# the hint says float, but nothing checks it
bad = Run('b', '0.05')
bad.lr * 2
Output:
'0.050.05'

A works like Go’s defer or Java’s try-with-resources, in block form: with path.open() as f: closes the file when the block ends, even if it raises. A makes its values one at a time, only when asked, so a file bigger than memory can be read line by line. Once it has run out it stays empty, and a second loop over it gets nothing, with no error.

from pathlib import Path
path = Path('data/runs.csv')
with path.open() as f:
    header = f.readline()
header, f.closed
Output:
('run,lr,loss\n', True)
def losses(path):
    with path.open() as f:
        next(f)  # skip the header
        for line in f:
            yield float(line.split(',')[2])
g = losses(path)
next(g)
Output:
0.42
list(g)
Output:
[0.31, 0.58]
list(g)
Output:
[]

Last, the setup every later chapter runs in. A is a folder of packages for one project, like node_modules with its own python. installs the pinned versions into it. , from Astral, can do the same job, and its docs describe it as extremely fast.

python3 -m venv ~/.venvs/python-for-ml     # or: uv venv ~/.venvs/python-for-ml
source ~/.venvs/python-for-ml/bin/activate
pip install -r requirements.txt            # or: uv pip install -r requirements.txt

You will also meet the , a document of code cells you run one at a time. This course uses plain .py files and pytest instead, because they diff cleanly and run anywhere.

The habit to keep: convert what comes from a file or a caller to the type you mean before you use it. only say what you meant; float() and int() make it so.

Homework

The packet first checks that your has Python 3.12 or newer and every pinned version. Then you read a CSV of training runs into , and with , fill in a and an , and finish with a , , and .

Chapter 1 workbook

Unzip, follow the README once to set up, then run pytest until it's green.