Kiru Lab  /  Foundations: Python as an Instrument  /  Python From First Principles

Repeating Work

Loops — the construct that turns a small amount of arithmetic into a trained model.

Code Walkthrough  ·  about 35 minutes

A `for` loop runs a block once for each item in a collection. A `while` loop runs it until a condition stops being true. Between them, these two account for essentially all repetition in this curriculum — including every training loop you will ever write.

for: when you know what you are iterating over
for token in ["the", "model", "learns"]:
    print(token.upper())

for i in range(5):              # 0, 1, 2, 3, 4 — stops before 5
    print(i)

for i, token in enumerate(["a", "b", "c"]):   # index and value together
    print(i, token)

for name, score in zip(["a", "b"], [0.9, 0.7]):   # two collections in step
    print(name, score)
while: when you do not know how many times
error = 10.0
steps = 0

while error > 0.001:
    error = error * 0.5
    steps = steps + 1
    if steps > 1000:            # always give a while loop an escape hatch
        print("did not converge")
        break

print(f"converged in {steps} steps")

That second example is the shape of every iterative solver in this lab. The inverse kinematics routine in track six is exactly this loop with a Jacobian inside it, and the same 1000-iteration safety cap for exactly the same reason.

Accumulating a result

The pattern you will use most
losses = []                     # start empty
for epoch in range(10):
    loss = train_one_epoch()    # do the work
    losses.append(loss)         # collect the result

average = sum(losses) / len(losses)

break and continue

  • `break` leaves the loop entirely — use it when you have found what you were looking for.
  • `continue` skips to the next iteration — use it to filter out cases you do not want to process.
  • A loop with several `break` points is usually a function that wants extracting.
Where this is going

In the next module you will learn to replace loops like these with array operations that run a hundred times faster. Write the loops first anyway. You cannot vectorize an operation you could not write out by hand, and when a vectorized line misbehaves the loop is how you check it.


Hold on to

  • for when you know the collection; while when you know the condition
  • Always give a while loop an iteration cap
  • Initialize, iterate, accumulate — the shape of every training loop

Work through

Try each one before opening the solution. Getting it wrong first is most of where the learning happens.

  1. Write a loop that sums the numbers 1 to 100 and prints the total. Then do it with `sum(range(...))` and confirm the answers match.
    Hint

    `range(1, 101)` stops before 101.

    Solution

    Both give 5050. Reach for the built-in in real code — it is faster and clearer — but write the loop at least once so you know what the built-in is doing.

    total = 0
    for n in range(1, 101):
        total += n
    print(total)             # 5050
    
    print(sum(range(1, 101)))  # 5050
    Check your work

    Paste this after your own code. If it runs without raising, you have it.

    assert sum(range(1, 101)) == 5050
    print("ok")
  2. Write the halving loop from the lesson as a function `steps_to_converge(start, target)`, and report how many steps it takes to get from 10.0 below 1e-6.
    Hint

    Count iterations in a variable you increment inside the loop.

    Solution

    It takes 24 steps: halving from 10.0, you need 10 * 0.5**n < 1e-6, so n > log2(10^7) which is about 23.3. Keep the iteration cap — an unbounded while loop with a condition that can never be satisfied is an infinite hang, and you will write one eventually.

    def steps_to_converge(start, target, max_steps=1000):
        value, steps = start, 0
        while value > target:
            value *= 0.5
            steps += 1
            if steps >= max_steps:
                raise RuntimeError("did not converge")
        return steps
    Check your work

    Paste this after your own code. If it runs without raising, you have it.

    assert steps_to_converge(10.0, 1e-6) == 24
    assert steps_to_converge(1.0, 0.5) == 1
    print("ok")
  3. Using `enumerate`, print each word of a sentence with its position, but skip any word shorter than four letters. Use `continue` rather than nesting an if.
    Hint

    `continue` jumps to the next iteration without running the rest of the body.

    Solution

    Test the skip condition first and `continue`, which leaves the interesting work unindented. This is the loop version of the guard clause, and it reads better than wrapping the body in an `if len(word) >= 4:`.

    sentence = "the model learns a compressed map of its training data"
    
    for i, word in enumerate(sentence.split()):
        if len(word) < 4:
            continue
        print(i, word)

Sign in to track your progress through the lab.