1.4.1 · D5Python & Scientific Computing

Question bank — Python syntax, data types, control flow

1,720 words8 min readBack to topic

Before we start, three tiny vocabulary anchors so nothing here is a symbol you haven't met:


True or false — justify

Cover the answer, decide true or false, then explain why before revealing.

True and 1 are equal when compared with ==.
True — bool is a subclass of int, so True == 1 and False == 0. That is why sum([True, True, False]) gives 2, which is handy for counting matches.
A tuple can never contain something mutable.
False — a tuple is immutable in which objects it points to, but if one of those objects is a list, that list can still be mutated. t = ([1,2],); t[0].append(3) works.
x = 5; y = x; y = 6 changes x to 6.
False — for immutable ints, y = 6 rebinds the name y to a new object; x still tags the original 5. Reference aliasing only shows through mutation, and ints can't be mutated.
Copying a list with b = a[:] protects a completely from changes to b.
False — that is a shallow copy. Top-level elements are duplicated, but nested lists are still shared. Use copy.deepcopy when the list holds mutable objects (e.g. layer weight lists).
0.1 + 0.2 == 0.3 is True in Python.
False — floats are IEEE-754 binary approximations, so 0.1 + 0.2 is 0.30000000000000004. Never test float equality directly; compare with a tolerance instead.
Indentation in Python is just a style choice like in C or Java.
False — whitespace is the grammar; it defines block boundaries. Wrong indentation raises IndentationError or silently changes which block a line belongs to.
if predictions: and if len(predictions) > 0: always behave the same.
Mostly true for lists — both are false on an empty list — but if predictions: is more general (works on any iterable) and is the Pythonic form. They differ if predictions is None, where the first is false and the second raises TypeError.
None == False evaluates to True.
False — None is its own singleton; it is falsy (acts false in an if) but it is not equal to False. None == False is False, while bool(None) is False.
An int in Python can overflow like in C.
False — Python int has arbitrary precision; it grows to fit any whole number limited only by memory. It is float (fixed 64-bit) that hits precision limits.
Adding a float and an int raises a type error.
False — numeric types promote: 5 + 2.0 gives 7.0. Python only refuses conversions between incompatible kinds (like str and int), not between numbers.

Spot the error

Read the snippet, name what breaks and why.

msg = "Epoch " + 5
TypeError — Python is strongly typed and will not implicitly turn 5 into text. Fix: "Epoch " + str(5) or use an f-string f"Epoch {5}".
steps = 1000 / 32 then feeding steps where an integer count is needed.
/ returns a float (31.25), and you cannot run a fractional number of batch steps. Use floor division // to get 31.
if acc > 0.95:
    print("done")
      save()

::: IndentationError: unexpected indentsave() is indented deeper than the sibling print with no reason (no new block opened). All lines in one block must align.

weights = [0.5, 0.3]
backup = weights
backup[0] = 0.9
Now weights[0] is...
0.9, not 0.5backup is a reference to the same list, so mutating through one name is visible through the other. backup = weights.copy() would isolate them.
def f():
    total += 1
total = 0
f()

::: UnboundLocalError — assigning to total inside f marks it local to f for the whole function, so += 1 reads a local that was never set. Add global total (or better, pass and return it).

for x in range(10):
    pass
print(x)
Is x defined after the loop?
Yes — 9. Loop variables leak into the surrounding scope in Python (unlike comprehensions). This can silently clobber an outer variable named x.
even = [x**2 for x in range(10) if x % 2 = 0]
SyntaxError= is assignment; the comparison needs ==. The filter clause must be a boolean expression.
d = {}
value = d["lr"]

::: KeyError: 'lr' — indexing a missing key raises, it does not return None. Use d.get("lr") (returns None) or d.get("lr", default) for a safe fallback.


Why questions

Cover the answer and try to reason it out first.

Why does Python use explicit type conversion instead of auto-coercing everything?
To prevent silent precision or meaning loss. Auto-converting float weights to int would truncate every value and quietly destroy a model; forcing int(...) makes the loss a deliberate choice.
Why is a tuple preferred over a list for something like input_shape=(28, 28, 1)?
It is a fixed configuration that should never change during a run; immutability makes accidental mutation impossible and lets it be used as a dict key (lists can't, tuples can).
Why does dict lookup being "O(1)" matter for storing hyperparameters or feature mappings?
It means retrieval time stays roughly constant no matter how many keys you store, so looking up a config or mapping a feature name is fast even with thousands of entries.
Why does a list comprehension often run faster than the equivalent for + append?
The comprehension's looping and appending happen in optimised C internals in one pass, avoiding the repeated Python-level .append method calls of the manual loop.
Why does steps_per_epoch = dataset_size // batch_size use floor division rather than rounding?
Training needs a whole number of batch updates; you cannot perform a fraction of a batch step, so you take the integer part deliberately (and handle the leftover partial batch separately if needed).
Why does Python search names in the LEGB order (Local → Enclosing → Global → Built-in)?
So the closest, most specific definition wins. A local learning_rate should shadow a global one — inner scopes override outer ones, and only if nothing matches anywhere do you get NameError.
Why is checking if my_list: considered more Pythonic than if len(my_list) != 0:?
Because emptiness is already encoded in the object's truthiness — an empty container is falsy — so the direct form reads as intent ("if there are items") and works for any iterable.

Edge cases

The scenarios the happy path skips.

What does range(0, 100, 32) actually produce, and does it include 100?
[0, 32, 64, 96]range stops before the end value, so 100 is excluded. The last batch (96–99) is a partial batch of 4, which your loop must be ready to handle.
What does int(3.7) give — 3 or 4?
3int() truncates toward zero, it does not round. int(-3.7) gives -3 for the same reason. Use round() if you actually want nearest-integer.
What happens with while condition: if the condition is True on entry and nothing inside ever changes it?
An infinite loop — while re-checks the condition every pass, so without a break or a change to the condition variable it never exits. This is the classic "training never stops" bug.
What is the boolean value of an empty string "", empty dict {}, 0, and 0.0?
All four are falsy — they act as False in an if. So if 0.0: skips its block, which can hide bugs when a legitimate zero (e.g. a zero loss or zero feature) is treated as "missing".
If a for loop's iterable is empty, does the loop body run at all?
No — zero iterations, the body is skipped entirely and control passes straight to whatever follows (or to the loop's else, if present). No error is raised.
Does an if/elif chain evaluate every condition, or stop early?
It stops at the first true condition, runs that block, and skips all remaining elif/else. That is why order matters: a broad condition placed first can mask a narrower one below it.
What is special about None compared to other falsy values like 0 or ""?
None is a unique singleton meaning "no value at all" (missing data, uninitialised weight), distinct from a real zero. Test it with is None, never == None, because identity is exact and faster.

Recall Self-check before you move on

Can you explain, in one breath each: (a) why b = a[:] still isn't safe for nested lists, (b) why True + True == 2, (c) why floats fail ==, and (d) what range(0, 10, 3) excludes? If any of these felt shaky, revisit the matching item above — then head to NumPy arrays and vectorization where these type and reference rules quietly power every array operation.