1.2.18 · D5Introduction to Programming (Python)
Question bank — for loop — iterating over sequences
True or false — justify
Iterating a string with for ch in "cat": gives you whole words.
False — a
str is a sequence of characters, so you get 'c', 'a', 't' one at a time; there are no word boundaries unless you .split() first.for k in some_dict: loops over the dict's values.
False — plain iteration yields the keys. Use
.values() for values or .items() for (key, value) pairs.A for loop must know the number of items before it starts.
False — it never counts ahead; it just calls
next() until StopIteration, so it works even on lazy/infinite-looking iterables like range or generators.After for i in range(3): pass, the name i no longer exists.
False — in Python the loop variable leaks into the surrounding scope and keeps its last value, so
i is 2.range(1, 5) includes both 1 and 5.
False —
range is half-open [1, 5), so it yields 1, 2, 3, 4; the stop value 5 is excluded.zip(a, b) raises an error if a and b have different lengths.
False — it silently stops at the shorter sequence; the extra items of the longer one are simply ignored.
enumerate(seq) changes the items of seq into numbers.
False — it pairs each item with a running index, yielding
(index, item) tuples; the original items are untouched.Every for loop can be rewritten as a while loop plus iter/next.
True — that desugaring is the definition of
for; it calls iter(seq) once, then next() in a loop until StopIteration triggers a break.The loop variable is a fresh copy, so rebinding it inside the body affects the sequence.
False — the loop variable is just a name pointing at the current item; rebinding the name (e.g.
x = 0) does not write back into the sequence.Two nested for loops over the same range always run the same number of body executions.
True — the inner loop restarts fully on every outer pass, so total runs = (outer count) × (inner count), a fixed product for fixed ranges.
Spot the error
nums = [1, 2, 3, 4]
for n in nums:
if n % 2 == 0:
nums.remove(n)What goes wrong, and why?
Removing while iterating shifts later items left, but the iterator's position keeps advancing — so it skips the item that slid into the vacated slot (here
4 survives). Loop over a copy nums[:] or build a new list.for i in range(len(items)):
print(items[i])Why is this considered un-Pythonic even though it works?
You reintroduced the very index bookkeeping the
for loop exists to remove; for item in items: is clearer and can't produce an off-by-one or IndexError.for x in seq:
x = x * 2
print(seq)Why is seq unchanged?
x = x * 2 only rebinds the local name x to a new value; it never reaches back into seq. To change the list you must assign by index or build a new list.d = {"a": 1, "b": 2}
for k, v in d:
print(k, v)Why does this raise an error?
Iterating
d yields single keys, not pairs, so Python can't unpack one string into k, v. Use for k, v in d.items():.for i in range(5, 1):
print(i)Why does the loop body never run?
With a default step of
+1, range(5, 1) can never move from 5 up to below 1, so it is empty — the loop body executes zero times (no error).total = 0
for n in nums
total += nWhat's the syntax problem?
The
for header must end with a colon :. Without it Python can't tell where the header stops and the indented body begins.Why questions
Why does for prefer signalling "done" with an exception (StopIteration) instead of a length check?
Because it works uniformly on things without a known length (files, generators, network streams); asking "any more?" via
next() is more general than "how many total?".Why is the loop variable said to always move forward, never restart mid-loop?
The iterator is created once (a single
iter() call) and only ever advances; nothing in the loop resets its cursor, so each item is delivered exactly once in order.Why is range described as "lazy"?
It does not build a list of all numbers up front; it computes each value on demand as
next() is called, so range(10**9) costs almost no memory.Why does looping a dictionary give keys rather than values by design?
Keys are the dict's primary index — the thing you look items up by — so iterating them lets you reach any value via
d[k] while also being the natural "identity" of each entry.Why does enumerate exist when you could keep your own counter with i += 1?
A manual counter is an extra line that can drift out of sync or be forgotten;
enumerate binds the correct index atomically with the item, so they can never disagree.Why does tuple-unpacking for i, v in enumerate(seq): work at all?
enumerate yields a 2-element tuple each pass, and Python's assignment splits that tuple into the two names — the same mechanism as a, b = (1, 2).Edge cases
Looping over an empty list for x in []: — how many times does the body run?
Zero times — the iterator is exhausted immediately, so
StopIteration fires on the very first next() and the loop ends cleanly without error.range(0) in a loop — what happens?
The range is empty (
[0, 0) contains nothing), so the body runs zero times; this is the safe "do nothing" case, not an error.After a loop over an empty sequence, is the loop variable defined?
No — since the body never ran, no assignment to the loop variable ever happened, so referencing it afterwards raises
NameError.Iterating a single-character string for ch in "x": — one item or none?
Exactly one iteration, binding
ch = 'x'; a string of length 1 is still a sequence with one element, not an empty one.zip() where one input is empty, e.g. zip([1, 2], []) — what does the loop do?
The shorter side has length 0, so
zip stops before the first pair; the loop body runs zero times.for i in range(3, 3): — included or empty?
Empty — with start equal to stop and a positive step there is no value in the half-open interval
[3, 3), so the body never runs.An iterator that was already fully consumed, looped again — what happens?
Nothing runs — an exhausted iterator keeps raising
StopIteration, so a second loop over the same iterator object is empty (unlike a list, which can be re-iterated freshly).Recall One-line summary of the traps
Most traps come from three habits: assuming stop is included (it isn't), assuming the loop variable is scoped/reset (it leaks and persists), and mutating the thing you iterate (the cursor drifts). Loop the data, don't fight it.
Connections
- for loop — iterating over sequences — the parent this bank drills.
- range function — source of the half-open
[start, stop)traps. - Lists and indexing — the mutate-while-looping hazard lives here.
- Strings as sequences — why looping a string yields characters.
- Dictionaries — keys, values, items — keys-vs-values trap.
- enumerate and zip — pairing and short-stopping behaviour.
- Iterators and generators — why exhausted iterators stay empty.
- while loop — condition-based repetition — the desugaring behind every
for.