Foundations — Dictionary and set comprehensions
This page assumes you have never seen {n: n ** 2 for n in range(5)}. We will not use any piece of it until that piece has a plain-words meaning AND a picture. By the end, the parent note will read like ordinary English.
The atoms, in build-order
We go from the most basic idea (a collection) up to the full comprehension. Each step reuses only what came before it.
1. A collection = a container that holds several things
The picture: imagine a row of egg-cups. Each cup holds one value; the whole tray has one name.
Why the topic needs it: comprehensions produce collections. If you don't picture "a box of things," the phrase "builds a whole collection in one expression" is meaningless.
2. Square brackets [ ] = a list (ordered, duplicates allowed)
- Ordered = there is a first, a second, a third; you can ask "what's at position 0?"
- Duplicates allowed = the same value can appear twice.
Why the topic needs it: the parent says "you already know list comprehensions." Lists are the starting shape. Dict and set comprehensions are described as "the same idea but different braces," so you must first know what the list version means.
3. The power operator ** = "raise to the power of"
Our very first preview line said n ** 2. Let's not leave that symbol unexplained.
Why the topic needs it: the parent's examples 1 and 2 build values with n ** 2 and n**2. That is the value-rule of the comprehension — miss the operator and every output looks wrong.
4. Curly braces { } = the shared brackets of dict AND set
Hold this thought: { } alone is ambiguous until you look for one tiny mark. That mark is the colon (next).
Why the topic needs it: the entire parent page is written in { }. Its headline claim ("the braces {} are shared, and the presence of a colon : decides dict-vs-set") is about this exact overloading — so you must know braces do double duty before that sentence means anything.
5. The colon : = "pairs this key with that value"
This is the single most important symbol on the whole parent page, so we build it carefully.
The picture: a two-column table. Left column = keys (the labels). Right column = values (the payloads). The colon is the arrow across each row.
{ "cat": 3 }— colon → a dictionary (a labelled lookup table).{ "cat", 3 }— no colon, just values separated by commas → a set.
Why the topic needs it: the parent's headline claim is "presence of a colon : is what tells Python this is a dict, not a set." You cannot verify that claim without knowing what a key:value pair is.
See Dictionaries in Python for the full data structure and Sets in Python for the no-duplicates box.
6. A set = a bag of distinct things, no order
The picture: a drawstring bag of stickers. Drop in two identical stickers and you still only have one — the bag refuses copies.
Why the topic needs it: set comprehensions exist because this auto-dedup is useful (example 4: unique word lengths). Without picturing "duplicates vanish," the point of a set comprehension is lost.
7. A tuple ( ) = a fixed row of comma-separated values
The picture: a stapled index card with a fixed number of blanks already filled in — you can read each blank, but you can't add a new one.
Why the topic needs it: d.items() (step 12) hands each row back as a tuple (key, value), and the parent's mistake (c) fix — "use a tuple" for a compound key — depends on knowing what a tuple is.
8. Iterable = anything you can walk through one item at a time
The picture: a conveyor belt. Items ride past you, one at a time, and each one gets processed as it arrives.
Why the topic needs it: every comprehension has the shape ... for item in ITERABLE .... The iterable is the conveyor belt that drives the whole factory.
9. range(n) = the counting conveyor 0, 1, 2, …, n−1
Why the topic needs it: nearly every worked example loops over a range(...). If you miscount its output, every result looks wrong.
10. for item in iterable = "take each item, in turn"
The picture: the conveyor belt again, but now a little tag reading n clips onto whichever box is currently in front of you.
Why the topic needs it: this is the "tail" of every comprehension — the engine. The parent's mechanical rule literally moves this clause to the back of the one-liner.
11. if condition = a gate that lets only some items through
%is the remainder operator:10 % 3is1(10 = 3·3 + 1).n % 2 == 0asks "is the remainder 0 when divided by 2?" → "isneven?".==is the equality test (returns True/False), not=which assigns. Two very different symbols.
The picture: a turnstile on the conveyor. Even boxes pass; odd boxes are rejected.
Why the topic needs it: the parent's example 2 filters with if n % 2 == 0. Confusing = (assign) with == (test) is a classic crash, so we separate them now.
12. The ternary A if cond else B = a value-chooser (NOT a filter)
See Ternary conditional expression for the full treatment.
Why the topic needs it: the parent's example 5 puts if/else in the value slot. Mixing it up with the filtering if is a top confusion — worth its own atom.
13. .items() and tuple unpacking for k, v in ...
The picture: the two-column table from step 5, read one row at a time — the left cell drops into k, the right cell into v.
Why the topic needs it: the parent's example 3 inverts a dict with {v: k for k, v in grades.items()}. Both .items() and the k, v unpacking must be understood first.
14. Hashable = "can be used as a key / put in a set"
Why the topic needs it: dict keys and set elements must be hashable. The parent's mistake (c) — {[1,2]: "a" ...} crashes — is exactly this rule. See Hashing and hashable types.
Putting the atoms together: the full skeleton
Now that each symbol has a meaning, snap them into one shape. A dict comprehension is four slots:
Read it left to right in plain English: "Make this key:value pair, for each var coming off the iterable, but only if the gate passes." Drop the colon and the key slot, and the very same skeleton becomes a set:
Trace the running preview {n: n ** 2 for n in range(5)} through the four slots:
key_expr=n→ look up by the number.value_expr=n ** 2→ store its square.for n in range(5)→ conveyor gives0,1,2,3,4.- (no
if) → every item passes.
Result: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}.
The duplicate-key gotcha
One edge case bites everyone the first time. In a dict comprehension, keys must be unique — so what happens if two items produce the same key?
A set comprehension has the mirror-image behaviour: duplicate values are also collapsed, but there you want that (it is the whole point). Same "keep-one" rule, opposite intent.
Equipment checklist
Cover the right side and test yourself — you are ready for the parent note only when every line is instant.
A collection is…
[ ] builds a…
** means…
n ** 2 is n × n, 2 ** 3 is 8Difference between * and **…
* multiplies (3*2=6); ** is exponent (3**2=9){ } are the brackets shared by…
The single mark that makes braces a dict not a set…
:A key vs a value is…
A set is…
{} (empty braces) creates a…
set() for an empty set)A tuple is…
(90, "Asha") — hashableAn iterable is…
range(5) produces…
for n in range(5) does what to n…
n % 2 == 0 asks…
% = remainder, == = equality test)Difference between = and ==…
= assigns a name; == tests equality (True/False)A filtering if goes…
A ternary A if cond else B goes…
d.items() yields…
(key, value) tuplefor k, v in d.items() does…
Hashable means…
Two items make the same dict key → …
Connections
- ← back to the parent topic
- List comprehensions — the
[]sibling you build on - Dictionaries in Python — the labelled lookup table
- Sets in Python — the no-duplicates bag
- Generator expressions — the lazy
()cousin - Hashing and hashable types — why keys must be hashable
- Ternary conditional expression — the value-chooser
if/else