Skip to content
VLSI Mentor

Python Part I · Module 5 · Lists

Lists Inside Lists

Chapter 5.1 said a list can hold any kind of value, and a list is a kind of value — so a list can hold lists. That sounds like a curiosity and is actually the most useful structure in the module, because it is how you hold a table. A row of results is a list; a table of results is a list of those. Chapter 5.6 ran into this accidentally and badly, when repeating a list of lists produced one row referred to twice. This chapter does it deliberately: how to write a table down, how to reach a single cell with two indexes, and the important difference between changing one cell and replacing a whole row.

Foundation13 min readPythonPart IFundamentalsListsNesting

Part I · Module 5 · Chapter 5.7 · Lists Inside Lists

1. A Table Is a List of Rows

One test result has several parts — a name, a status, a runtime. That is a list:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
row = ["smoke", "PASS", 12.4]

Several results is a list of those:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
table = [["smoke", "PASS"], ["reset", "FAIL"]]
print(table)
print(len(table))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[['smoke', 'PASS'], ['reset', 'FAIL']]
2

Read that length carefully, because it is the first thing to get right. len(table) is 2, not 4. The outer list has two elements, and each of those elements happens to be a list of two strings. Counting elements means counting at one level.

Nothing new is happening. Chapter 5.1 said an element can be any value; here the values are lists.

2. Seeing the Structure

Written on one line, a nested list is hard to read. Written out with one row per line it explains itself, and Python accepts the line breaks inside the brackets:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    ["smoke", "PASS", 12.4],
    ["reset", "FAIL", 8.7],
]
print(results)
print(len(results))
print(len(results[0]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[['smoke', 'PASS', 12.4], ['reset', 'FAIL', 8.7]]
2
3

Two rows of three. The trailing comma after the last row is allowed and is worth adopting — adding a row later becomes a one-line change.

The structure, drawn out:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  results                      (a list — 2 elements)
    ├─ [0] ──→ ["smoke", "PASS", 12.4]     (a list — 3 elements)
    │           ├─ [0] "smoke"
    │           ├─ [1] "PASS"
    │           └─ [2] 12.4
    └─ [1] ──→ ["reset", "FAIL", 8.7]      (a list — 3 elements)
                ├─ [0] "reset"
                ├─ [1] "FAIL"
                └─ [2] 8.7

There are two levels of numbering and each level starts at zero independently. The outer numbers pick a row; the inner numbers pick a position within that row.

3. Reaching a Cell

Two indexes get you one value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
table = [["smoke", "PASS"], ["reset", "FAIL"]]
print(table[1][0])
print(table[0][1])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
reset
PASS

Chapter 5.2 gave the rule this depends on and it has not changed: each bracket applies to whatever is on its left. Here it is with the middle step given a name so you can see it happen:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
table = [["smoke", "PASS"], ["reset", "FAIL"]]
row = table[1]
print(row)
print(row[0])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['reset', 'FAIL']
reset

table[1] evaluates first and produces a list — the second row. Then [0] is applied to that list and produces its first element. table[1][0] is those two steps written without stopping in between; it is not a single two-dimensional lookup.

So the order is always: row first, then position within the row. table[1][0] is row 1, item 0.

And because the middle result is an ordinary list, everything from the module applies to it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
table = [["smoke", "PASS"], ["reset", "FAIL"]]
print(len(table[0]))
print(table[0][-1])
print(table[1][:1])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2
PASS
['reset']

len of a row, negative indexing within a row, a slice of a row — none of which needed new rules.

4. A Realistic Table

With three columns, reaching a specific cell means knowing what each position means:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    ["smoke", "PASS", 12.4],
    ["reset", "FAIL", 8.7],
]
print(results[1][2])
print(results[0][0])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
8.7
smoke

results[1][2] is row 1, position 2 — the runtime of the reset test. results[0][0] is the name of the first test.

5. Changing a Cell, and Changing a Row

Everything Chapter 5.3 established about mutation applies at both levels, and the two operations look almost identical.

Change one cell — index to the row, then assign to a position in it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    ["smoke", "PASS", 12.4],
    ["reset", "FAIL", 8.7],
]
print(results[1])
results[1][1] = "PASS"
print(results[1])
print(results)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
['reset', 'FAIL', 8.7]
['reset', 'PASS', 8.7]
[['smoke', 'PASS', 12.4], ['reset', 'PASS', 8.7]]

One value changed, in place, inside the second row. The table still has two rows of three.

Replace a whole row — assign to a position in the outer list:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    ["smoke", "PASS", 12.4],
    ["reset", "FAIL", 8.7],
]
results[0] = ["boot", "PASS", 1.0]
print(results)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[['boot', 'PASS', 1.0], ['reset', 'FAIL', 8.7]]

The difference is one bracket. results[0] = [...] puts a new list at position 0 of the table; results[0][1] = "..." reaches into the list already there and changes one of its elements.

And adding a row is just append at the outer level:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    ["smoke", "PASS", 12.4],
    ["reset", "FAIL", 8.7],
]
results.append(["burst", "FAIL", 20.1])
print(len(results))
print(results[-1])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3
['burst', 'FAIL', 20.1]

Three rows now, and results[-1] is the last one — negative indexing at the outer level, picking a row.

6. Rows Need Not Match

Python does not require the inner lists to be the same length, because it does not know they are meant to be rows:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ragged = [["a"], ["b", "c", "d"]]
print(len(ragged), len(ragged[0]), len(ragged[1]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2 1 3

Two elements at the outer level, of length 1 and 3. This is allowed and is occasionally what you want. When it is not what you want — a table where one row is short — nothing will warn you, and the failure appears later as an IndexError on the row that happens to be short:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
t = [["a"]]
print(t[1][0])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndexError: list index out of range

Note which bracket failed. t[1] is out of range at the outer level — there is only one row — and the second bracket never ran. When a nested access raises, work out which level is at fault before changing anything; printing the intermediate value, as Section 3 did, is the fastest way.

7. Membership Looks at Whole Elements

Chapter 5.1 said in on a list compares whole elements. Nesting is where that bites:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
table = [["smoke", "PASS"], ["reset", "FAIL"]]
print(["smoke", "PASS"] in table)
print("smoke" in table)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False

The first is True — ["smoke", "PASS"] really is one of the two elements.

The second is False, and it looks wrong. "smoke" is plainly in there. But it is not an element of table — the elements of table are two lists, and neither of them is equal to the string "smoke". Membership does not search inside elements.

8. What Is Missing

You can now build a table, reach any cell, change a cell, replace a row and add a row. What you cannot yet do is work through the whole table without naming each row by number — checking every result, counting the failures, printing each line of a report.

That is iteration, and for a nested structure it is nested iteration: going through the rows, and for each row going through its items. Module 10 — Loops covers it, and Chapter 10.11 deals specifically with nested structures. The structure you have built here is what those loops will be given; nothing you have learned in this chapter changes when they arrive.

9. Predict, Run, Observe

Predict every line before reading on. Two of these are the traps.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grid = [["a", "b"], ["c", "d"], ["e", "f"]]
print(len(grid))
print(len(grid[0]))
print(grid[2][1])
print(grid[-1][0])
print(grid[0][-1])
print("c" in grid)
print(["c", "d"] in grid)
print(grid[1])

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3
2
f
e
b
False
True
['c', 'd']

len(grid) is 3 — three rows, not six values. grid[2][1] is row 2, position 1: "f". grid[-1][0] is the last row's first item, "e" — negative indexing works at either level. grid[0][-1] is the first row's last item, "b".

The two membership lines are Section 7: "c" is not an element of grid, so False; the row ["c", "d"] is, so True. If you predicted True for "c" in grid you expected in to look inside the rows, which it never does.

10. Exercises

Exercise 1 — build a table. Write a list of three rows, each holding a signal name and a width as a number. Print the whole table, its length, and the length of the first row.

Exercise 2 — reach the cells. For t = [["clk", 1], ["addr", 32], ["data", 64]], write expressions producing: the name in the second row, the width in the last row, the first row as a whole, and the number of rows.

Exercise 3 — predict. Given m = [[1, 2, 3], [4, 5, 6]], give each result without running it, then verify: (a) len(m) (b) len(m[1]) (c) m[0][2] (d) m[1][-1] (e) m[-1][0] (f) m[0]

Exercise 4 — cell or row. Starting from t = [["a", "b"], ["c", "d"]] each time, say what the table contains after each, and which level was changed: (a) t[0][1] = "x" (b) t[0] = ["x"] (c) t.append(["e", "f"]) (d) t[1][0] = "x"

Exercise 5 — which bracket failed. This raises. Say which level is out of range, how you could tell, and what the corrected line would be if you wanted the last row's first item:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t = [["a", "b"], ["c", "d"]]
print(t[2][0])

Exercise 6 — membership. For table = [["smoke", "PASS"], ["reset", "FAIL"]], say True or False for each and explain the two that surprise you: (a) "reset" in table (b) ["reset", "FAIL"] in table (c) "reset" in table[1] (d) "FAIL" in table[0]

Exercise 7 — the column problem. A colleague stores results as ["smoke", "PASS", 12.4] and reads the runtime with row[2]. A new column is inserted before the runtime. Say what breaks, whether Python reports it, and one sentence on what you would do about it.

11. Summary

  • A list can hold lists, which is how a table of rows is stored.
  • len() counts at one level. A two-row table of three columns has len() 2, not 6.
  • Write one row per line with a trailing comma — it reads as a table and adding a row stays a one-line change.
  • Each bracket applies to whatever is on its left. table[1][0] evaluates table[1] to a row, then takes that row's first element. It is two operations, not one lookup.
  • Row first, then position within the row.
  • A row is an ordinary list — len, negative indexing, slicing and every method all work on it.
  • table[0][1] = x changes a cell; table[0] = [...] replaces a row. One bracket apart, different operations.
  • table.append(row) adds a row, and table[-1] is the last one.
  • Rows need not be the same length. Nothing warns you; the failure arrives later as an IndexError.
  • When a nested access raises, find out which level failed by printing the intermediate value.
  • in compares whole elements and never looks inside them. "smoke" in table is False; "smoke" in table[0] is True.
  • Position numbers carry no meaning. Keep the column order documented, and note that Modules 6 and 8 offer better structures for records.

Two chapters have now shown one list being reachable two ways. The next chapter explains it.

Where this fits

Part of the Python curriculum.