Python Part I · Module 5 · Lists
Going Through a List
You can build a list, reach into it and change it. What you cannot yet do is the thing lists exist for: perform the same work on every element without naming each one by hand. Three tests is tedious; three hundred is impossible. This chapter introduces the one piece of syntax that solves it — a for loop over a list — and deliberately introduces nothing else. The question here is narrow and practical: how do I visit each element of the list I have just learned? A later module takes loops apart as a general capability, with counting, waiting, stopping early and all the rest. For now, one shape, understood properly.
Foundation14 min readPythonPart IFundamentalsListsIteration
Part I · Module 5 · Chapter 5.4 · Going Through a List
1. The Problem a List Eventually Creates
Everything in this module so far has been about holding and shaping data. You can write down a list of tests, reach any element, sort it, add to it. But look at what it still takes to actually do something to every element:
tests = ["smoke", "reset", "interrupt"]
print(tests[0])
print(tests[1])
print(tests[2])smoke
reset
interruptThat works, and it is wrong in two ways that matter.
It does not survive a change. Add a fourth test and the code silently prints only three — no error, just a missing line. Remove one and it raises IndexError.
And it does not scale. This is three lines for three tests. A real test list has hundreds, and a log file has thousands of lines.
What you want is to say "do this to every element" once, without knowing or caring how many there are. That is what this chapter is for.
2. The Smallest Loop
Here is the whole idea in three lines:
tests = ["smoke", "reset", "interrupt"]
for test in tests:
print(test)smoke
reset
interruptThree elements in the list, three lines of output, and the print was written once.
This is new syntax, so take it one piece at a time:
for— a keyword that starts the loop. It tells Python that what follows is work to be repeated.test— a name, which you choose. On each pass it will refer to one element of the list.in— a keyword separating the name from the thing being gone through. This is part of theforsyntax; it is not the membership operator from Chapter 3.6, even though it is spelled the same.tests— the list to go through.:— a colon ending the line, which says "the block that follows belongs to this."- the indented line — the body of the loop: the work done once per element.
Read aloud, for test in tests: is "for each element of tests, call it test, and then do this."
3. The Loop Variable Is a Name
This is the part to get right, and it needs nothing new — only Chapter 2.3's model.
test is an ordinary name, and it refers to a value. What the loop does on each pass is re-point that name at the next element:
| Pass | test refers to | Output |
|---|---|---|
| 1 | "smoke" | smoke |
| 2 | "reset" | reset |
| 3 | "interrupt" | interrupt |
| — | list exhausted | loop ends |
The body — print(test) — ran once per pass, three times in all, with test meaning something different each time.
Nothing is being copied into a container, and test is not a special kind of variable. It is the same thing tests = [...] made on line 1 — a name referring to a value — and the loop simply assigns to it repeatedly.
One consequence worth knowing, because it surprises people: the name still exists after the loop finishes, referring to whatever it was pointed at last.
last = ["a", "b", "c"]
for item in last:
print(item)
print(item)a
b
c
cThe fourth line is c — the loop variable survived, holding the final element. That is not usually something to rely on, but it explains the result if you ever print a loop variable afterwards and wonder why it has a value.
4. Order Comes From the List
Chapter 5.1 established that order is part of a list's value. Iteration simply follows it:
tests = ["interrupt", "smoke", "reset"]
for test in tests:
print(test)interrupt
smoke
resetSame three names as Section 2, written in a different order, visited in that different order. The loop has no opinion about ordering — it walks the list from the first element to the last. If you want a particular order, put the list in that order first, which is what Chapter 5.5 was for.
Duplicates are visited separately, for the same reason they were counted separately:
statuses = ["PASS", "FAIL", "PASS"]
for status in statuses:
print(status)PASS
FAIL
PASSThree elements, three passes. The loop does not notice that two of them are equal.
5. Zero, One and Many
The number of passes is simply the number of elements. That sounds obvious and has one genuinely useful consequence.
One element, one pass:
tests = ["smoke"]
for test in tests:
print(test)
print("done")smoke
doneNo elements, no passes:
failures = []
for failure in failures:
print(failure)
print("done")doneThe body never ran. Not an error, not a warning — there were no elements, so there was no work, and the program continued to the next line.
That is worth pausing on, because it is the single most useful property of a loop for real scripts:
| Elements in the list | Times the body runs |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 3 | 3 |
| N | N |
6. The Body Is Everything Indented
Chapter 1.10 established that indentation marks a block in Python. The loop body is exactly that: every line indented under the for line.
tests = ["smoke", "reset"]
for test in tests:
print("running")
print(test)
print("finished")running
smoke
running
reset
finishedRead the output carefully. running appears twice, because both indented lines are part of the body and the whole body repeats once per element. finished appears once, at the end, because it is not indented — it is not part of the loop, so it runs after the loop has finished.
That is the whole rule: indented under the for line means repeated; back at the original margin means after.
7. Broken on Purpose
Four failures, all executed. These are the ones you will actually cause.
Forgetting to indent the body. Python needs a block and there is not one:
tests = ["smoke", "reset"]
for test in tests:
print(test) File "bad.py", line 3
print(test)
^^^^^
IndentationError: expected an indented block after 'for' statement on line 2The message is unusually specific and worth reading in full: it names what it expected (an indented block), what it was for (a for statement) and where that statement was (line 2).
Forgetting the colon:
tests = ["smoke", "reset"]
for test in tests
print(test) File "bad.py", line 2
for test in tests
^
SyntaxError: expected ':'Python read the whole for line, reached the end, and found no colon. The caret points past the last character — at the place the colon should have been.
Indenting inconsistently inside the body:
tests = ["smoke", "reset"]
for test in tests:
print("running")
print(test) File "bad.py", line 4
print(test)
IndentationError: unexpected indentThe first body line set the indentation; the second went deeper, and Python has no meaning for that. Every line of one block sits at the same depth.
Looping over something that has no elements to give:
for x in 4210:
print(x)TypeError: 'int' object is not iterableThat word iterable is the one Chapter 5.9 introduced: a value that can supply its contents one at a time. A list is iterable; a single number is not, because it has no contents to hand out. A for loop needs something iterable on the right of in.
8. Doing Something With Each Element
Printing is the smallest possible body. The loop becomes useful when the body does real work, and every operation you already know is available because test is an ordinary value.
String methods from Module 4 work directly:
tests = ["smoke", "reset", "interrupt"]
for test in tests:
print(test.upper())SMOKE
RESET
INTERRUPTAnd here is a point worth checking rather than assuming — the list itself is untouched:
tests = ["smoke", "reset", "interrupt"]
for test in tests:
print(test.upper())
print(tests)SMOKE
RESET
INTERRUPT
['smoke', 'reset', 'interrupt']Still lowercase. That is Chapter 4.11 doing exactly what it promised: upper() returns a new string and changes nothing. The loop read each element; it did not modify any of them.
len() works on each element, because each element is a string:
signals = ["clk", "reset_n", "valid"]
for signal in signals:
print(signal)
print(len(signal))clk
3
reset_n
7
valid
5Tests from Chapter 4.10:
paths = ["rtl/top.sv", "tb/top_tb.sv"]
for path in paths:
print(path.endswith(".sv"))True
TrueArithmetic, when the elements are numbers:
runtimes = [12.4, 8.7, 19.2]
for runtime in runtimes:
print(runtime * 2)24.8
17.4
38.4Nothing in any of those is new except the two loop lines around them. That is the point of this chapter: the loop does not change what you can do to a value, it changes how many values you can do it to.
Building a second list
Combining the loop with append from Chapter 5.3 gives you the shape most real scripts have — go through one list, build another:
src = ["smoke", "reset"]
upper = []
for test in src:
upper.append(test.upper())
print(upper)
print(src)['SMOKE', 'RESET']
['smoke', 'reset']Start with an empty list, add one element per pass, and the original is untouched. Chapter 5.3 called this the accumulation pattern and said the part that decides when to append would come later; this is the part that supplies the repetition.
9. The Loop Variable Is the Element, Not Its Position
This single confusion breaks more beginner loops than anything else in the chapter, so it is worth being blunt about.
signals = ["clk", "reset_n"]
for signal in signals:
print(signal, type(signal))clk <class 'str'>
reset_n <class 'str'>signal refers to the element — the string "clk", then the string "reset_n". It does not refer to 0 and then 1.
So there is no indexing to do. print(signal) is correct; print(signals[signal]) would be nonsense, because signal is a string and a list position must be a whole number — exactly the TypeError Chapter 5.2 showed.
10. Predict, Run, Observe
Predict all of it before reading on: how many times the body runs, what test refers to on each pass, the order of the output, and where done appears.
tests = ["smoke", "reset"]
for test in tests:
print(test.upper())
print("done")The verified output:
SMOKE
RESET
doneTwo elements, so the body ran twice. test referred to "smoke" then "reset". The output follows list order. And done appears last and once, because that line is not indented — Section 6.
Now a second one, where the body has two lines:
runtimes = [12.4, 8.7]
for runtime in runtimes:
print(runtime)
print(runtime + 1)
print("end")12.4
13.4
8.7
9.7
endNote the interleaving. Both body lines run for the first element before either runs for the second — the whole body completes each pass. If you predicted 12.4 / 8.7 / 13.4 / 9.7 you read the loop as running each line all the way down the list, which is not what happens.
11. Do Not Add or Remove While Looping
One warning, because Module 5 taught you how to change a list and this is where that gets you into trouble.
Reading the list during a loop is completely fine:
items = ["a", "b", "c"]
for item in items:
print(item, len(items))
print(items)a 3
b 3
c 3
['a', 'b', 'c']No problem at all. The body looked at the list on every pass and nothing surprising happened.
Changing the list's structure while looping over it is a different matter:
items = ["a", "b", "c"]
for item in items:
print(item)
items.remove(item)
print(items)a
c
['b']Look at what happened. The loop printed a and c — b was never visited — and the list still has an element left in it. Nothing raised, nothing warned.
The reason is that the loop is walking the list by position while you are changing which elements sit at which position. Removing "a" shifts "b" into the position the loop has already passed, so the next pass goes to what is now position 1 — "c" — and "b" is skipped entirely.
That is deliberately a rule rather than an explanation of every case. There are correct ways to remove items while processing a collection, and they use tools from later modules. Until then, the second-list approach handles everything you will need and cannot go wrong.
12. What Is Still Missing
You can now perform the same operation on every element of a list. Two things are still out of reach, and both are deliberate.
You cannot yet do something different for some elements — print only the failures, skip the comment lines. Choosing is if, and Module 9 covers it.
And this chapter has shown you exactly one loop shape: going through a list, one element at a time, from first to last. Module 10 — Loops takes loops apart as a capability in their own right: counting without a list, repeating while you wait for something, stopping early, walking two collections together, and what the same loop does when given a collection that is not a list.
Everything you have learned here stays true when those arrive. A list is the thing most loops go through, which is why the loop had to be introduced here at all.
13. Exercises
Exercise 1 — the smallest loop. Write the three lines that print each of ["clk", "reset_n", "valid"] on its own line. Then predict, without running it, how many lines of output a list of seven signals would produce.
Exercise 2 — predict the order. Give the exact output of each, then verify:
for status in ["PASS", "FAIL", "PASS"]:
print(status.lower())for runtime in [12.4, 8.7]:
print(runtime + 1)Exercise 3 — how many passes. For each list, say how many times the body runs:
(a) ["a", "b", "c"] (b) ["a"] (c) [] (d) ["a", "a"] (e) a list of 250 paths
Exercise 4 — indented or not. Predict the exact output, then run it. Say which lines repeat and why:
tests = ["smoke", "reset"]
for test in tests:
print("start")
print(test)
print("stop")Then move print("stop") to be indented with the others and predict the new output before running it.
Exercise 5 — fix the indentation. This raises. Write down the exact error, say what Python expected, and fix it:
paths = ["rtl/top.sv"]
for path in paths:
print(path)Exercise 6 — element, not position. A colleague writes this and gets a TypeError. Explain in two sentences what they assumed the loop variable was, what it actually is, and give the corrected body:
signals = ["clk", "reset_n"]
for signal in signals:
print(signals[signal])Exercise 7 — use the element. Given paths = ["rtl/top.sv", "tb/top_tb.sv", "doc/readme.md"], write a loop that prints True or False for each depending on whether it ends in .sv. Predict the three lines of output before running it.
Exercise 8 — build a second list. Starting from names = ["smoke", "reset"], write a loop that builds a new list holding the length of each name, then print both lists. Say in one sentence why the original is unchanged.
Exercise 9 — the empty list. Explain in two sentences why a loop over [] prints nothing from its body and does not raise, and why that is useful in a script that reports failures.
Exercise 10 — the dangerous one. Predict the output and the final list, then run it and explain the discrepancy in three sentences:
items = ["a", "b", "c"]
for item in items:
print(item)
items.remove(item)
print(items)14. Summary
- A
forloop runs its body once for each element of a list, in list order. It is how a stored collection becomes work actually done. for test in tests:reads as "for each element oftests, call ittest, and do this."- The loop variable is an ordinary name. On each pass Python points it at the next element — the same name-and-value model as every other assignment.
- It refers to the element, not to its position.
signals[signal]is the classic mistake. - Order comes from the list, and duplicates are visited separately.
- The number of passes is the number of elements: 0 elements means the body never runs, which is not an error and needs no special handling.
- The body is everything indented under the
forline. Unindented lines after it run once, when the loop has finished. IndentationError: expected an indented blockmeans you forgot to indent the body;SyntaxError: expected ':'means you forgot the colon.TypeError: 'int' object is not iterablemeans the thing afterinhas no contents to hand out.- Every operation you already know works inside the body — string methods,
len(), tests, arithmetic — because the loop variable is an ordinary value. - A loop that reads elements does not change the list.
upper()still returns a new string. - Going through one list and appending to another is the shape most scripts have.
- Do not add or remove elements from the list you are looping over. It silently skips items. Build a second list instead.
Module 5 is now complete. The next module takes the collection you know well and removes one property from it.
Where this fits
Part of the Python curriculum.
