Python Part I · Module 10 · Loops
Looping Over Strings, Lists, Tuples, Sets and Dictionaries
Modules 4 to 8 built five collections, each with its own purpose and its own access rules. A list finds things by position, a dictionary by key, a set answers only whether something is present. It would be reasonable to expect five ways of looping over them. There is one. The same for statement walks every collection in the language, which is a real piece of design rather than a convenience — it means the loop you learned in Chapter 10.1 is the last loop you need to learn. What differs is only what a single pass hands you, and for two of the five that answer catches people out: one gives you something other than what you asked for, and one gives it in an order you cannot depend on.
Foundation16 min readPythonPart IFundamentalsLoopsCollections
Part I · Module 10 · Chapter 10.3 · Looping Over Strings, Lists, Tuples, Sets and Dictionaries
1. The Same Statement, Five Times
Nothing below is new syntax. It is the one loop from Chapter 10.1, pointed at five different things:
for ch in "axi":
print(ch)a
x
ifor item in ["smoke", "reset"]:
print(item)smoke
resetfor field in ("axi_smoke", 4210):
print(field)axi_smoke
4210Three collections, three loops that differ in nothing but the thing after in. The remaining two — sets and dictionaries — work identically and need a paragraph each about what they hand you.
2. A String Hands You Characters
Chapter 4.4 established that a string is a sequence, so looping it gives one character per pass:
signal = "clk_i"
for ch in signal:
print(ch)c
l
k
_
iEach item is itself a string of length one — Python has no separate character type:
for ch in "ax":
print(ch, type(ch), len(ch))a <class 'str'> 1
x <class 'str'> 1That means every string method is available on each character, which makes per-character checks read plainly:
name = "data_q2"
digits = 0
for ch in name:
if ch.isdigit():
digits = digits + 1
print("digits in the name:", digits)digits in the name: 13. A List Hands You Items
The straightforward case, and the one everything else is compared against:
widths = [1, 8, 32]
for width in widths:
print(width, "bits")1 bits
8 bits
32 bitsAn item can be anything, including another collection — Section 9 returns to that.
4. A Tuple Hands You Items, in Order
A tuple iterates exactly like a list, and its fixed order is guaranteed:
record = ("axi_smoke", 4210, "PASS")
for field in record:
print(field)axi_smoke
4210
PASSLooping over the fields of one record is usually not what you want, though, because the fields mean different things. The common and useful case is a list of tuples, where each pass hands you one whole record — and Chapter 6.5's unpacking can take it apart in the for line itself:
results = [("axi_smoke", 4210, "PASS"), ("axi_burst", 4211, "FAIL")]
for test, seed, status in results:
print(test, "at seed", seed, "->", status)axi_smoke at seed 4210 -> PASS
axi_burst at seed 4211 -> FAILThree names on the left of in, one tuple per pass, unpacked automatically. This is the same unpacking as a, b = (1, 2); the loop simply does it on every pass.
5. A Set Hands You Members, in No Dependable Order
A set iterates like everything else:
failed = {"reset", "crc", "burst"}
for name in sorted(failed):
print(name)burst
crc
resetNote the sorted(). It is there because the order a set hands you its members is not dependable, and that is not a theoretical caution. Running a loop that collects the members of {"reset", "crc", "burst"} in twelve fresh interpreters produced all six possible orders — three runs gave one order, three gave another, and the remaining four orders appeared twice, twice, once and once.
Chapter 7.2 established why: a set's layout depends on hash values, and Python randomises string hashing per process. The consequence for a loop is concrete — a report generated by iterating a set directly will list its entries differently on different runs, and a test that compares two such reports will fail intermittently for no reason connected to the design.
When order genuinely does not matter — counting, or checking membership — iterate the set directly and nothing is lost:
failed = {"reset", "crc", "burst"}
long_names = 0
for name in failed:
if len(name) > 3:
long_names = long_names + 1
print("failures with names longer than three characters:", long_names)failures with names longer than three characters: 2The count is the same whatever order the loop visited them in, so this is safe. The rule is simple: if the output depends on the order, sort first.
6. A Dictionary Hands You Keys
This is the one that catches everyone:
status = {"smoke": "PASS", "reset": "FAIL"}
for key in status:
print(key)smoke
resetKeys, not values, and not pairs. The loop variable's name has no influence on this whatsoever:
status = {"smoke": "PASS", "reset": "FAIL"}
for value in status:
print(value)smoke
resetCalling it value printed the keys anyway. Nothing raised, nothing warned, and a program that goes on to compare value against "PASS" will simply never match.
The order is dependable here — dictionaries preserve insertion order, as Chapter 8.1 established — so unlike a set, iterating a dictionary directly gives a repeatable result.
To reach the value, look it up:
status = {"smoke": "PASS", "reset": "FAIL"}
for test in status:
print(test, "->", status[test])smoke -> PASS
reset -> FAILThat is correct and it does a lookup per pass to reach something the loop could have handed you directly. Section 7 is the better form.
7. .items(), .keys() and .values()
Chapter 8.5 introduced the three views. In a for line they are at their most useful:
status = {"smoke": "PASS", "reset": "FAIL"}
print("--- keys ---")
for test in status.keys():
print(test)
print("--- values ---")
for result in status.values():
print(result)
print("--- items ---")
for test, result in status.items():
print(test, "->", result)--- keys ---
smoke
reset
--- values ---
PASS
FAIL
--- items ---
smoke -> PASS
reset -> FAIL.items() hands you a two-field tuple per pass, and the two names before in unpack it — Section 4's mechanism, applied to the most common case in the language.
8. What Each Collection Hands You
| Collection | One pass gives you | Order | Notes |
|---|---|---|---|
str | one character, as a str of length 1 | left to right, guaranteed | split first if you want words |
list | one item | positional, guaranteed | the straightforward case |
tuple | one field | positional, guaranteed | unpack a list of tuples in the for line |
set | one member | not dependable | sort if the output depends on order |
dict | one key | insertion order, guaranteed | use .items() for key and value |
dict.values() | one value | matches key order | no way back to the key |
dict.items() | a (key, value) tuple | insertion order | unpack it with two names |
range | one integer | ascending or descending by step | Chapter 10.4 |
9. Looping Over a Nested Structure
Chapter 8.7 built results that are a list of dictionaries. Looping over it hands you one dictionary per pass, and everything you know about dictionaries then applies:
results = [
{"test": "axi_smoke", "seed": 4210, "status": "PASS"},
{"test": "axi_burst", "seed": 4211, "status": "FAIL"},
{"test": "reset_seq", "seed": 4212, "status": "FAIL"},
]
failed = []
for result in results:
if result["status"] == "FAIL":
failed.append(result["test"])
print("failed:", failed)
print(len(failed), "of", len(results), "failed")failed: ['axi_burst', 'reset_seq']
2 of 3 failedOne loop, not two. The loop walks the outer list; reaching inside each item is a lookup, not another loop. Needing a loop inside a loop is a different situation and Chapter 10.11 covers it.
10. Predict, Run, Observe
# A
for ch in "ok":
print(ch)
# B
pairs = [(1, "a"), (2, "b")]
for number, letter in pairs:
print(number, letter)
# C
d = {"x": 10, "y": 20}
total = 0
for thing in d:
total = total + len(thing)
print("total:", total)
# D
d = {"x": 10, "y": 20}
total = 0
for thing in d.values():
total = total + thing
print("total:", total)The verified output:
o
k
1 a
2 b
total: 2
total: 30A — two characters.
B — each item is a two-field tuple, unpacked by the two names.
C — total: 2. The loop walked the keys, which are the one-character strings "x" and "y", so len(thing) was 1 each time. Nothing about this raises.
D — total: 30. .values() is what C should have said.
C and D differ by seven characters and give answers that are not even the same kind of thing. That is the whole reason this chapter exists.
11. Exercises
Exercise 1 — five loops. Write one loop for each of "hi", [1, 2], (3, 4), {"a", "b"} and {"k": "v"} that prints each item. For the set, make the output dependable. Say in one sentence what the dictionary loop printed.
Exercise 2 — count the digits. Given names = ["data_q2", "clk", "addr_r15"], count how many digits appear across all names. You will need a loop over the list and a loop over each name — write it with the inner one as a per-character check, and say which of the two is doing the per-character work.
Exercise 3 — the wrong name. Write for value in {"a": 1}: and print the loop variable. Explain in two sentences why it printed what it did and why no error appeared.
Exercise 4 — unpack in the for line. Given results = [("smoke", "PASS"), ("reset", "FAIL")], print smoke: PASS and reset: FAIL using unpacking in the for statement rather than indexing.
Exercise 5 — order dependence. For each, say whether the result depends on set iteration order: (a) counting the members (b) printing them one per line (c) checking whether "crc" is among them (d) building a comma-joined string of them. Fix the ones that depend on it.
Exercise 6 — three views. For widths = {"clk": 1, "data": 32, "addr": 16}, print the names only, the widths only, and the pairs — using the right view for each. Then print the total of all widths.
Exercise 7 — list of dictionaries. Using Section 9's results, build a list of the seeds of the failing tests, and print the test name of the first failure. State how many loops you needed.
Exercise 8 — predict the type. For each loop, say what type(item) would print on the first pass: (a) for item in "ab" (b) for item in [(1, 2)] (c) for item in {"a": 1} (d) for item in {"a": 1}.items(). Verify all four.
12. Summary
- One
forstatement walks every collection in the language. Only what a pass hands you differs. - A string hands you one character at a time, each a
strof length 1 — split first if you want words. - A list and a tuple hand you items in guaranteed order.
- A list of tuples can be unpacked in the
forline with one name per field. - A set hands you members in no dependable order — twelve runs over a three-string set produced all six possible orders. Sort when the output depends on order; iterate directly when it does not.
- A set of small integers iterating in order is an artifact, not a promise.
- A dictionary hands you keys. Naming the loop variable
valuechanges nothing and raises nothing. - Dictionary order is dependable (insertion order), unlike a set's.
- Use
.items()when you need the key and the value,.values()when you need only values, and plainfor key in d:when you need only keys. - Looping a nested structure hands you one whole inner object per pass; reaching inside it is a lookup, not another loop.
The next chapter takes the one collection in the table that has not been explained yet, and settles why range(10) stops at 9.
Where this fits
Part of the Python curriculum.
