1.4.1 · D4Python & Scientific Computing

Exercises — Python syntax, data types, control flow

2,283 words10 min readBack to topic

Before we start, one picture ties everything together — how Python decides which block of code runs.

Figure — Python syntax, data types, control flow

Read the figure like a road: you enter at the top, each diamond asks a yes/no question, and you follow the first branch whose answer is "yes". That single idea (checked top to bottom, stop at first match) explains almost every control-flow trap on this page.


Level 1 — Recognition

You are shown code; you predict the exact output. No cleverness, just knowing the rules.

Exercise 1.1 — int vs float division

a = 7 / 2
b = 7 // 2
c = 7 % 2
print(a, b, c)

What is printed, and what type is each of a, b, c?

Recall Solution

WHAT each operator does

  • / is true division — it always returns a float, even when the numbers divide evenly. So 7 / 2 = 3.5.
  • // is floor division — it divides then rounds down to the nearest whole number, and stays an int when both inputs are int. So 7 // 2 = 3.
  • % is modulo — the remainder left after floor division. , so the remainder is 1.

Output: 3.5 3 1 Types: a is float, b is int, c is int.

WHY it matters: in a training loop, number-of-steps must be a whole number, so you reach for //. Using / gives you 3.5 — meaningless as a step count.

Exercise 1.2 — Truthiness

values = [0, "", [], "0", [0], None]
for v in values:
    print(bool(v))

Predict all six lines.

Recall Solution

Python calls something falsy if it counts as False in a condition. The falsy family is: the number 0, empty containers ("", [], {}, set()), and None. Everything else is truthy.

Go item by item:

  • 0False (the number zero)
  • ""False (empty string)
  • []False (empty list)
  • "0"True — this is a string of length 1, not the number zero. It contains a character, so it is non-empty.
  • [0]True — a list with one element. The element happens to be 0, but the list is non-empty.
  • NoneFalse

Output: False, False, False, True, True, False.


Level 2 — Application

Now you compute a real answer that an ML script would need.

Exercise 2.1 — Steps per epoch

A dataset has dataset_size = 1000 samples and batch_size = 32. Using floor division, how many full batches fit in one epoch, and how many samples are left over in a final partial batch?

Recall Solution
  • Full batches: 1000 // 32. Since and , the answer is 31.
  • Leftover: 1000 % 32 = 1000 - 992 = 8.

Answer: 31 full batches, plus 8 leftover samples (one partial batch). WHY floor division: you cannot run 0.25 of a weight update, so the count must be an integer — exactly what // guarantees.

Exercise 2.2 — Batch start indices

Predict the exact list produced by

list(range(0, 1000, 32))

and state how many elements it has.

Recall Solution

range(start, stop, step) produces start, start+step, start+2·step, … and stops before reaching stop. Here: 0, 32, 64, … up to the largest multiple of 32 that is still < 1000.

That largest value is 992 (since 1024 ≥ 1000 is excluded). The values are for — that is 32 elements, the numbers 0, 32, 64, …, 992.

Answer: first few are [0, 32, 64, 96, ...], last is 992, total length 32. Note this is one more than the 31 full batches — the 32nd start index (992) begins the partial batch of the leftover 8 samples.

Exercise 2.3 — Step-decay learning rate

Using the parent note's schedule, compute lr for epoch = 50, initial_lr = 0.1:

if epoch < 30:   lr = initial_lr
elif epoch < 60: lr = initial_lr * 0.1
elif epoch < 90: lr = initial_lr * 0.01
else:            lr = initial_lr * 0.001
Recall Solution

Conditions are checked top to bottom, stop at first true (follow the road in the figure above).

  • 50 < 30? No.
  • 50 < 60? Yes → run this block, then skip the rest.

lr = 0.1 * 0.1 = 0.01. Answer: lr = 0.01.


Level 3 — Analysis

Here you must reason about why Python behaves as it does, especially around references.

Exercise 3.1 — Aliasing vs copy

w = [0.5, 0.3, 0.8]
backup = w
backup[0] = 0.9
print(w[0])

What prints, and why?

Recall Solution

backup = w does not copy the list. It makes backup a second name pointing at the same list object (this is reference semantics — see the figure below).

Figure — Python syntax, data types, control flow

Both names point to one box in memory. Editing through backup edits the one and only list, so w sees it too. Output: 0.9. To get an independent copy you need backup = w.copy() (shallow) or copy.deepcopy(w) (for nested structures).

Exercise 3.2 — Shallow copy limitation

grid = [[1, 2], [3, 4]]
shallow = grid.copy()
shallow[0][0] = 99
print(grid[0][0])

Does grid change? Explain.

Recall Solution

.copy() makes a shallow copy: a new outer list, but its elements are the same inner list objects. So shallow[0] and grid[0] are still the same inner list. Editing shallow[0][0] edits that shared inner list. Output: 99 — yes, grid changes. Only copy.deepcopy(grid) would rebuild the inner lists too, giving full independence.

Exercise 3.3 — LEGB name resolution

lr = 0.01
def train():
    lr = 0.001
    def update():
        print(lr)
    update()
train()

What prints, and which scope wins?

Recall Solution

Python resolves a name by the LEGB order: Local → Enclosing → Global → Built-in, using the first match. Inside update, lr is not local (never assigned there). Python steps out to the Enclosing function train, where lr = 0.001 exists. Match found — it stops there and never reaches the global 0.01. Output: 0.001.


Level 4 — Synthesis

Combine loops, comprehensions, and conditions into one result.

Exercise 4.1 — Filtered comprehension

Evaluate exactly:

[x ** 2 for x in range(10) if x % 2 == 0]
Recall Solution

Read it as a loop: "for each x in 0..9, keep it only if x % 2 == 0 (even), then output x**2." Even values of x: 0, 2, 4, 6, 8. Their squares: 0, 4, 16, 36, 64. Answer: [0, 4, 16, 36, 64].

Exercise 4.2 — Nested loop count

count = 0
for i in range(3):
    for j in range(i + 1):
        count += 1
print(count)

What is count?

Recall Solution

The inner loop runs i + 1 times for each i:

  • i = 0 → inner runs 1 time
  • i = 1 → inner runs 2 times
  • i = 2 → inner runs 3 times

Total: . Answer: 6.

Exercise 4.3 — while-loop convergence

loss = 1.0
steps = 0
while loss > 0.1:
    loss = loss / 2
    steps += 1
print(steps, loss)

How many steps, and the final loss?

Recall Solution

Each pass halves loss. Track it (the condition is re-checked before each pass):

  • start 1.0 > 0.1 → halve → 0.5, steps 1
  • 0.5 > 0.1 → 0.25, steps 2
  • 0.25 > 0.1 → 0.125, steps 3
  • 0.125 > 0.1 → 0.0625, steps 4
  • 0.0625 > 0.1? No → loop stops.

Answer: steps = 4, loss = 0.0625.


Level 5 — Mastery

One problem that only works if every earlier idea is solid.

Exercise 5.1 — Mini training-schedule audit

dataset_size = 1000
batch_size   = 32
initial_lr   = 0.1
 
steps_per_epoch = dataset_size // batch_size
starts = list(range(0, dataset_size, batch_size))
 
def lr_at(epoch):
    if epoch < 30:   return initial_lr
    elif epoch < 60: return initial_lr * 0.1
    elif epoch < 90: return initial_lr * 0.01
    else:            return initial_lr * 0.001
 
total_updates = 0
for epoch in range(3):          # epochs 0, 1, 2
    total_updates += len(starts)
 
print(steps_per_epoch,
      len(starts),
      lr_at(45),
      total_updates)

Predict all four printed values.

Recall Solution

Solve each piece with the tools above:

  1. steps_per_epoch = 1000 // 32 = 31 (Ex 2.1).
  2. len(starts)range(0,1000,32) has 32 elements (Ex 2.2). Note this differs from steps_per_epoch by one, because the 32nd start begins the partial batch of 8 leftover samples.
  3. lr_at(45): 45 < 30? no. 45 < 60? yes0.1 * 0.1 = 0.01 (Ex 2.3 logic).
  4. total_updates: the loop runs for epoch in 0,1,2 → 3 epochs, each adding len(starts) = 32. Total .

Output: 31 32 0.01 96.

The lesson: steps_per_epoch (31) and the number of batch starts (32) are not the same number — one uses // (full batches), the other uses range (which includes the partial batch's start). Confusing them under- or over-counts your updates.


Recall Quick self-check (cloze)

/ always returns type ::: float 7 // 2 equals ::: 3 7 % 2 equals ::: 1 bool([0]) is ::: True The name-resolution order is ::: LEGB (Local, Enclosing, Global, Built-in) range(0, 1000, 32) has how many elements ::: 32 b = a for a list makes b a ::: reference (alias) to the same object, not a copy

Where to go next: these ideas power NumPy arrays and vectorization (replacing slow loops with array ops), Python functions and lambda (scope and LEGB in depth), and Python iterators and generators (what for really does under the hood). Reference semantics reappear everywhere in Pandas DataFrames and Object-oriented programming in Python.