1.2.35 · D2Introduction to Programming (Python)

Visual walkthrough — Dictionary and set comprehensions

1,836 words8 min readBack to topic

Before any code, three plain words we will keep pointing at:


Step 1 — The undeniable starting point: an empty box

WHAT. We build a dictionary of squares the slowest, most obvious way — make an empty container, then fill it.

squares = {}          # an empty dict — the box

WHY. We must start from something no one can argue with. {} is an empty dict (remember the empty-braces trap: {} is a dict, set() is a set). Right now it holds nothing — zero key→value arrows.

PICTURE. The box on the left is empty. The conveyor belt on the right holds the numbers 0,1,2,3,4 that range(5) will feed us, one at a time. Nothing has moved yet.

Figure — Dictionary and set comprehensions

Step 2 — One item, one arrow

WHAT. Take the first item off the belt, n = 0, and store one entry: key 0 points to value 0*0 = 0.

n = 0
squares[n] = n * n    # squares[0] = 0

Term by term, on squares[n] = n*n:

  • squares :::: the box we are filling.
  • [n] :::: which slot — the key, here 0.
  • = n*n :::: what goes in that slot — the value, here 0.

WHY. This is the atom of the whole idea: one item off the belt creates one key→value arrow. Everything else is just repeating this.

PICTURE. A yellow arrow now runs from the button labelled 0 (the key) to the snack labelled 0 (the value). The belt has advanced by one.

Figure — Dictionary and set comprehensions

Step 3 — Let the loop do it five times

WHAT. Wrap Step 2 in a for loop so every item on the belt gets its arrow.

squares = {}
for n in range(5):        # walk the belt: 0,1,2,3,4
    squares[n] = n * n    # one arrow per pass

WHY. The loop is just Step 2 repeated. Pass 1 makes 0→0, pass 2 makes 1→1, pass 3 makes 2→4, and so on. The loop drives the box; nothing new is invented — only repetition.

PICTURE. All five arrows are drawn now: 0→0, 1→1, 2→4, 3→9, 4→16. Notice the loop has three moving parts, colour-coded: the belt source range(5) (blue), the key n (yellow), the value n*n (green).

Figure — Dictionary and set comprehensions

Step 4 — The fold: rearrange, delete nothing

WHAT. Take the three coloured pieces from Step 3 and slide them into one line, changing their order but not their meaning.

squares = {n: n*n for n in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

WHY. Read the comprehension left to right: it says the result-rule first ("n : n*n, i.e. this key paired with this value") and the source last ("for each n on the belt"). That reversed order is the only new thing to get used to — the machinery is identical to the loop.

PICTURE. The three coloured blocks from the loop physically migrate: key n and value n*n fly up to the front (with a colon between them), the belt for n in range(5) slides to the back. Same arrows, same result — just re-packed into one line.

Figure — Dictionary and set comprehensions

Step 5 — Add a gate: the filtering if

WHAT. Only let even numbers onto the assembly line by attaching an if at the end.

{n: n**2 for n in range(1, 11) if n % 2 == 0}
# {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

Term by term on the tail for n in range(1,11) if n % 2 == 0:

  • for n in range(1,11) :::: the belt now carries 1..10.
  • if n % 2 == 0 :::: a gaten % 2 is the remainder after dividing by 2; == 0 means "no remainder", i.e. even.

WHY. The if runs before the pair is built. If n fails the gate, no arrow is ever drawn for it — the odd numbers fall off the belt and never reach the box.

PICTURE. A gate sits on the conveyor. Even numbers (green) pass through and get their arrow; odd numbers (red) are bounced off before they can pair. The gate is at the end of the sentence — you read the source, then the gate, then know what survived.

Figure — Dictionary and set comprehensions

Step 6 — Drop the colon: same animation, now a set

WHAT. Delete the key and the colon, keep only the value. The factory now builds a set.

words = ["cat", "dog", "tiger", "ox"]
{len(w) for w in words}
# {2, 3, 5}

WHY. With no colon, each pass produces one value (len(w)), not a pair. A set stores only distinct values, so when "cat" and "dog" both give length 3, the second one is silently ignored — the set already has a 3. (See Sets in Python for why sets deduplicate, and Hashing and hashable types for how they know a value is "already there".)

PICTURE. Two lengths of 3 head into the set; the first is placed, the second bounces off the wall of an already-occupied slot. Compare the top row (dict: pairs with colon) to the bottom row (set: single values, no colon) — one glyph is the whole difference.

Figure — Dictionary and set comprehensions

Step 7 — The degenerate & edge cases (never let the reader get surprised)

WHAT. Three boundary situations the animations must cover.

(a) Empty belt. If the iterable is empty, zero passes happen, so you get an empty collection — an empty dict {} or an empty set().

{n: n*n for n in range(0)}   # {}   (belt had nothing)
{x for x in []}              # set()

(b) Colliding keys — later wins. Two items producing the same key do not coexist; the later pass overwrites the earlier.

{x % 3: x for x in range(6)}
# expected 6 entries, got 3: {0: 3, 1: 4, 2: 5}
# key 0 got x=0 then x=3 -> 3 wins; key 1 got 1 then 4 -> 4; key 2 got 2 then 5 -> 5

(c) Unhashable key — a crash, not a silent bug. A key must be hashable; a list is not.

{[1, 2]: "a"}   # TypeError: unhashable type: 'list'
# fix: use a tuple, which IS hashable
{(1, 2): "a"}   # fine

WHY. These are exactly the moments a beginner thinks "the comprehension is broken." It isn't — it is obeying dict rules: unique hashable keys, later-writer-wins. A set version of (b) simply keeps one copy of each value by the same logic. (Ternary value-choosers like ("even" if n%2==0 else "odd") live in the value slot — see Ternary conditional expression.)

PICTURE. Left panel: the empty belt → empty box. Middle panel: two arrows aimed at key 0, the second (green) overwriting the first (faded), leaving one entry. Right panel: a list-shaped key hitting a red barrier stamped "unhashable", with the tuple-shaped key passing.

Figure — Dictionary and set comprehensions

The one-picture summary

Everything on this page compressed into a single frame: a belt feeds items through an optional gate; each survivor becomes either a key:value pair (colon present → dict) or a single value (no colon → set); dicts enforce unique hashable keys, sets enforce distinct values.

Figure — Dictionary and set comprehensions
Recall Feynman retelling — say it in one breath

Picture a conveyor belt feeding numbers one at a time. You stand at the belt with a box. For each number, you decide two things: what button it gets (the key) and what snack it produces (the value) — that's the key : value at the front. The for ... part is just the belt telling you which numbers arrive. If you add an if at the very end, that's a gate: numbers that fail it fall off before you ever pair them. If you drop the colon, you stop making pairs and just toss single values into a bag that refuses duplicates — that's a set. Two items wanting the same button? The later one shoves the earlier one out. Try to use a list as a button? The machine jams, because buttons must be hashable. That whole story is what one line {k: v for x in xs if c} is doing at C speed instead of you doing it slowly in Python.

Connections

  • 1.2.35 Dictionary and set comprehensions (Hinglish) — parent topic, the rule stated
  • List comprehensions — same fold, produces a list with []
  • Dictionaries in Python — the structure built by the colon form
  • Sets in Python — the structure built by the no-colon form
  • Generator expressions — same head/tail shape but lazy, in (...)
  • Hashing and hashable types — why keys/elements must be hashable
  • Ternary conditional expression — the value-slot if/else