Skip to content
VLSI Mentor

Python Part I · Module 12 · Comprehensions

List and Dictionary Comprehensions

Chapter 12.1 folded an accumulator loop into a list comprehension and named its three parts. The same fold works for the other collection an automation script builds constantly — a mapping from test name to result, from signal to width, from severity to count. The syntax changes by two characters, the anatomy does not change at all, and the decision about which one you want turns out to be a question about the output expression rather than about the data. There is also one way a dictionary comprehension can lose data that a list comprehension cannot, and it does so without any sign at all.

Intermediate15 min readPythonPart IFundamentalsComprehensionsDictionaries

Part I · Module 12 · Chapter 12.2 · List and Dictionary Comprehensions

1. The List Form, Recapped

From Chapter 12.1, with the canonical filter — everything that is not a pass:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    {"test": "axi_smoke", "status": "PASS"},
    {"test": "axi_burst", "status": "FAIL"},
    {"test": "reset_seq", "status": "FATAL"},
]

fails = [r for r in results if r["status"] != "PASS"]

print(len(fails))
print([r["test"] for r in fails])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2
['axi_burst', 'reset_seq']

Note the output expression here is r itself — the whole result dictionary, not one field. A comprehension's output can be any expression, including the item unchanged, which makes this a pure filter that keeps complete records.

2. Which Bracket You Want

The second collection needs two characters' difference:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    {"test": "axi_smoke", "status": "PASS"},
    {"test": "axi_burst", "status": "FAIL"},
]

by_test = {r["test"]: r["status"] for r in results}

print(by_test)
print(by_test["axi_burst"])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'axi_smoke': 'PASS', 'axi_burst': 'FAIL'}
FAIL

Curly braces, and a colon in the output expression. The source and filter clauses are unchanged — for r in results, with an optional if — because Chapter 12.1's anatomy is the same anatomy.

The output decides the bracket:

Output expressionBracketsResult
one value per item[ ... ]a list
key: value per item{ ... : ... }a dict

That is the whole rule. If you can say what each item should contribute — one name or a name and its status — the form follows.

3. A Map Is Usually What You Actually Wanted

The list form answers which tests failed. The dict form answers what happened to test X — which is the question a report usually needs, and Chapter 8.1 argued at length that a key beats a position for exactly this.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    {"test": "axi_smoke", "status": "PASS"},
    {"test": "axi_burst", "status": "FAIL"},
    {"test": "reset_seq", "status": "FATAL"},
]

by_test = {r["test"]: r["status"] for r in results}

print(by_test["reset_seq"])
print("axi_burst" in by_test)
print(sorted(by_test))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
FATAL
True
['axi_burst', 'axi_smoke', 'reset_seq']

Lookup by name, membership by name, and sorted() for a dependable report order — all of Module 8's dictionary behaviour, from a one-line build.

4. Filtering a Dictionary Comprehension

The if goes in the same place and does the same job:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
results = [
    {"test": "axi_smoke", "status": "PASS", "seed": 4210},
    {"test": "axi_burst", "status": "FAIL", "seed": 4211},
    {"test": "reset_seq", "status": "FAIL", "seed": 4212},
]

failed_seeds = {r["test"]: r["seed"] for r in results if r["status"] == "FAIL"}

print(failed_seeds)
print(len(failed_seeds), "of", len(results), "kept")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'axi_burst': 4211, 'reset_seq': 4212}
2 of 3 kept

Read it as a sentence: the test name mapped to its seed, for each result, if it failed. The key and the value can come from different fields, and neither has to be the field being filtered on.

5. Building a Map From Two Lists

When the data arrives as parallel lists rather than records, Chapter 10.9's zip supplies the pairs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["axi_smoke", "axi_burst", "reset_seq"]
statuses = ["PASS", "FAIL", "FATAL"]

by_test = {name: status for name, status in zip(tests, statuses)}

print(by_test)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'axi_smoke': 'PASS', 'axi_burst': 'FAIL', 'reset_seq': 'FATAL'}

Two names in the source clause unpack each pair — Chapter 6.5's unpacking, which works in a comprehension exactly as it does in a for header.

For this exact case Chapter 10.9 already gave a shorter form, and it is better:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["axi_smoke", "axi_burst"]
statuses = ["PASS", "FAIL"]

print(dict(zip(tests, statuses)))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'axi_smoke': 'PASS', 'axi_burst': 'FAIL'}

Reach for the comprehension when the key or value needs transforming; use dict(zip(...)) when they do not. And remember Chapter 10.9 Section 8: zip truncates to the shortest argument silently, so strict=True belongs here too if the lists are meant to match.

6. Duplicate Keys Are Dropped Silently

This is the one real hazard, and a list comprehension has no equivalent.

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
results = [
    {"test": "axi_smoke", "status": "FAIL"},
    {"test": "axi_burst", "status": "PASS"},
    {"test": "axi_smoke", "status": "PASS"},
]

by_test = {r["test"]: r["status"] for r in results}

print(by_test)
print(len(results), "results became", len(by_test), "entries")
print("axi_smoke ->", by_test["axi_smoke"])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'axi_smoke': 'PASS', 'axi_burst': 'PASS'}
3 results became 2 entries
axi_smoke -> PASS

Three results in, two entries out. axi_smoke appears twice — it failed, then was rerun and passed — and the map reports only PASS.

Keys are unique, as Chapter 8.2 established, so the second axi_smoke overwrote the first. The survivor is the last one seen, because the comprehension walks the source in order and each assignment replaces the previous.

Whether "last wins" is right depends entirely on your data. For a rerun it may be exactly right. For two different simulators it is a lost row.

7. Transforming a Dictionary You Already Have

A dict can also be the source. Chapter 10.3 established that iterating one gives keys and .items() gives pairs, and both work here.

Filtering a mapping:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
widths = {"clk": 1, "rst_n": 1, "data": 32, "addr": 16}

wide = {name: w for name, w in widths.items() if w > 8}

print(wide)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'data': 32, 'addr': 16}

Transforming the values:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
widths = {"clk": 1, "data": 32}

labelled = {name: str(w) + " bits" for name, w in widths.items()}

print(labelled)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'clk': '1 bits', 'data': '32 bits'}

And inverting one — swapping keys and values:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
codes = {"PASS": 0, "FAIL": 1, "FATAL": 2}

by_code = {code: name for name, code in codes.items()}

print(by_code)
print(by_code[2])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{0: 'PASS', 1: 'FAIL', 2: 'FATAL'}
FATAL

Inverting is the place Section 6's hazard bites hardest: it is safe only if the original values were unique. Two statuses sharing a code would collapse into one entry.

8. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
nums = [1, 2, 3]
print("A:", {n: n * n for n in nums})

# B
d = {"a": 1, "b": 2}
print("B:", [k for k in d], [v for v in d.values()])

# C
pairs = [("x", 1), ("y", 2), ("x", 3)]
print("C:", {k: v for k, v in pairs})

# D
d = {"a": 1, "b": 2, "c": 3}
print("D:", {k: v for k, v in d.items() if v > 1})

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: {1: 1, 2: 4, 3: 9}
B: ['a', 'b'] [1, 2]
C: {'x': 3, 'y': 2}
D: {'b': 2, 'c': 3}

A — the key and the value both come from the same item, which is fine; they are two independent expressions.

B — a dict as a source gives keys; .values() gives values.

C — "x" appears twice and the last wins, so 3 not 1. Section 6, in four characters.

D — .items() plus a filter on the value.

9. Exercises

Exercise 1 — build both. From a list of result dictionaries, build (a) a list of the failing test names and (b) a map of test name to status. Print the length of each.

Exercise 2 — choose the bracket. For each, say which form you want and write it: (a) every seed used (b) each test's runtime (c) the names of tests over ten minutes (d) each signal mapped to whether it is wide.

Exercise 3 — the missing colon. Write {r["test"] for r in results} and print its type(). Say what you got and which chapter covers it.

Exercise 4 — lose a row. Build a map from a list containing two results for the same test name. Print the map and both lengths. Say which result survived and why.

Exercise 5 — detect the loss. Add a one-line check to Exercise 4 that reports whether any rows were dropped. Then change the key so nothing collides.

Exercise 6 — filter a mapping. Given widths, build a new dict of only the single-bit signals. Then build a list of their names. Say which you would return from a function and why.

Exercise 7 — invert it. Invert {"PASS": 0, "FAIL": 1}, then invert {"a": 1, "b": 1} and explain the second result in one sentence.

Exercise 8 — from two lists. Given parallel name and width lists, build the mapping twice — once with a comprehension and once with dict(zip(...)). Say when the comprehension is the better choice.

10. Summary

  • A dictionary comprehension uses curly braces and a colon in the output; the source and filter clauses are identical to a list comprehension's.
  • The output decides the bracket — one value per item is a list, a key: value pair is a dict.
  • Braces without a colon build a set, not a dictionary, and nothing raises.
  • The key and value can come from different fields, and from fields the filter does not mention.
  • A dict is often what you actually wanted, because a report asks what happened to test X.
  • dict(zip(a, b)) beats a comprehension when neither half needs transforming.
  • Duplicate keys are dropped silently and the last one wins. Three results can become two entries with no error.
  • Compare len() against the source when you believe keys are unique, or key on something that genuinely is.
  • A dict can be the source too — plain iteration gives keys, .items() gives pairs — which is how you filter, transform or invert a mapping.
  • Inverting is safe only if the original values were unique.

The next chapter looks more closely at the two jobs these are doing: keeping some items, and reshaping them.

Where this fits

Part of the Python curriculum.