Learning Goals
3 minBy the end of this lesson you can:
- Write
{key: value for x in seq}dict comprehensions. - Write
{x for x in seq}set comprehensions. - Recognise
{}as empty-dict syntax — and rememberset()for empty-set. - Combine filters and conditional values with these comprehensions.
Warm-Up · Three Brackets, Three Comprehensions
5 min[ x for x in seq ] → list comprehension
{ x for x in seq } → set comprehension
{ k: v for x in seq } → dict comprehension
( x for x in seq ) → generator expressionThe bracket type chooses the container. The body is the same — filter, nesting, conditional value all work identically.
One syntax, four containers. Once you know list comprehensions, dict and set come almost for free.
New Concept · The Two New Shapes
14 minDict comprehension — key: value
# Square each number, keyed by itself squares = {n: n * n for n in range(1, 6)} print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25} # Build a name → length dict names = ["Aisyah", "Wei Jie", "Priya"] name_len = {name: len(name) for name in names} # {'Aisyah': 6, 'Wei Jie': 7, 'Priya': 5}
Dict comprehension over .items()
Often you start with a dict and transform it. Use .items() to iterate (k, v) pairs:
prices = {"nasi lemak": 8.0, "teh tarik": 3.5, "cendol": 5.0} # Apply 10% discount to every price discounted = {item: price * 0.9 for item, price in prices.items()} # Flip the dict — values become keys flipped = {price: item for item, price in prices.items()} # WARNING: only works cleanly if all values are unique
Filter in a dict comprehension
# Only items costing more than RM 5 expensive = {item: price for item, price in prices.items() if price > 5} # → {'nasi lemak': 8.0}
Set comprehension — unique items
# Unique starting letters of names first_letters = {name[0] for name in ["Aisyah", "Aman", "Wei Jie", "Aizat"]} # → {'A', 'W'} -- A appears once even though three names start with it # Unique squared remainders mods = {n * n % 10 for n in range(20)} # → {0, 1, 4, 5, 6, 9}
Sets dedupe automatically. Use a set comprehension when the question is "what distinct values come out?"
The empty trap — same as PY-L2-08
empty_dict = {} # this is a dict empty_set = set() # set() — NOT {}
{} means empty dict because dicts came first historically. To create an empty set, you must call set(). Comprehension syntax avoids the ambiguity because the body distinguishes — but bare {} means dict.
The three-shape gallery
# Same data, three containers nums = [1, 2, 3, 4, 5] print([n * n for n in nums]) # list: [1, 4, 9, 16, 25] print({n: n * n for n in nums}) # dict: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25} print({n * n for n in nums}) # set: {1, 4, 9, 16, 25} print(sum(n * n for n in nums)) # gen: 55 (sum streaming)
Worked Example · The Letter Frequency Table
12 minSave as letter_freq.py:
# letter_freq.py — comprehensions on text text = "the quick brown fox jumps over the lazy dog" # 1 — unique letters (set comprehension) letters = {ch for ch in text if ch.isalpha()} print(f"Unique letters: {len(letters)}") # 26 (pangram!) # 2 — letter → count (dict comprehension) counts = {ch: text.count(ch) for ch in letters} # 3 — sort and display for ch, n in sorted(counts.items(), key=lambda x: -x[1])[:5]: print(f" {ch}: {n}") # 4 — vowels only (filter in dict comp) vowel_counts = {ch: n for ch, n in counts.items() if ch in "aeiou"} print(f"\nVowels: {vowel_counts}") # 5 — Letters by case (with conditional value) classified = {ch: "vowel" if ch in "aeiou" else "consonant" for ch in letters} v_count = sum(1 for v in classified.values() if v == "vowel") print(f"\nVowels found: {v_count} / Consonants: {len(classified) - v_count}")
Output
Unique letters: 26
o: 4
e: 3
h: 2
t: 2
u: 2
Vowels: {'e': 3, 'i': 1, 'o': 4, 'u': 2, 'a': 1}
Vowels found: 5 / Consonants: 21Read the diff
Three comprehensions, three different containers. Set for "what unique letters?". Dict for "count of each". Another dict for "classified by type". The filter (if ch.isalpha()) and conditional value ("vowel" if ... else "consonant") work the same way as in list comps.
Try It Yourself
13 minBuild {n: n * 7 for n in range(1, 13)} — the seven times table.
Hint
table = {n: n * 7 for n in range(1, 13)} print(table[5]) # 35 print(table)
Given {"a": 1, "b": 2, "c": 3}, build a flipped dict where values become keys.
Hint
d = {"a": 1, "b": 2, "c": 3} flipped = {v: k for k, v in d.items()} print(flipped) # {1: 'a', 2: 'b', 3: 'c'}
Works because all values are unique. If they weren't, later entries would overwrite earlier ones — the flipped dict would shrink.
Given lines = ["the cat sat", "on the mat", "the dog ran"], build a set of unique words across all lines using a nested set comprehension.
Hint
lines = ["the cat sat", "on the mat", "the dog ran"] words = {w for line in lines for w in line.split()} print(words) # {'the', 'cat', 'sat', 'on', 'mat', 'dog', 'ran'}
Two for-clauses: outer over lines, inner over the words in each line. The set automatically dedupes "the".
Mini-Challenge · The Stock Snapshot
8 minBuild stock_snapshot.py. Given:
stock = [ {"name": "pencil", "price": 0.50, "qty": 120, "kind": "stationery"}, {"name": "eraser", "price": 0.80, "qty": 60, "kind": "stationery"}, {"name": "milo", "price": 4.50, "qty": 25, "kind": "drinks"}, {"name": "kopi", "price": 3.00, "qty": 35, "kind": "drinks"}, {"name": "apple", "price": 1.00, "qty": 40, "kind": "fruit"}, {"name": "durian", "price": 8.50, "qty": 5, "kind": "fruit"}, ]
Produce:
price_map— dict name → price.kinds— set of distinct kinds.expensive— dict of name → price for items costing > RM 2.low_stock_names— set of names where qty < 30.value_by_kind— dict kind → total value across items in that kind. (Trickier — may need a small loop. Try a comprehension overkindsfirst.)
Show one possible solution
# stock_snapshot.py — five comprehensions price_map = {s["name"]: s["price"] for s in stock} kinds = {s["kind"] for s in stock} expensive = {s["name"]: s["price"] for s in stock if s["price"] > 2} low_stock_names = {s["name"] for s in stock if s["qty"] < 30} # Trickier: dict comp over kinds, with an inner sum-comp filtering by kind value_by_kind = { kind: sum(s["price"] * s["qty"] for s in stock if s["kind"] == kind) for kind in kinds } for label, value in [ ("price_map", price_map), ("kinds", kinds), ("expensive", expensive), ("low_stock_names", low_stock_names), ("value_by_kind", value_by_kind), ]: print(f"{label:<18}: {value}")
Non-negotiables: each of the 5 reports is a single comprehension. The last one nests a generator expression inside a dict comprehension — that's a Level-3 idiom.
Recap
3 minDict and set comprehensions extend yesterday's shape. {k: v for ...} builds a dict — common for grouping, mapping, and flipping. {x for ...} builds a set — common for deduplication. Both support filters and conditional values, just like list comps. The empty case differs: {} is empty dict; use set() for empty set. One syntax, four containers.
Vocabulary Card
- dict comprehension
{key: value for ... in ...}. Builds a new dict.- set comprehension
{x for ... in ...}. Builds a new set; deduplicates automatically.- .items() iteration
- Standard way to iterate (key, value) pairs of an existing dict.
- flipped dict
- A dict with keys/values swapped.
{v: k for k, v in d.items()}.
Homework
4 minBuild roman.py. Define ROMAN = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} by hand. Then:
- Build the inverse dict (number → Roman letter) using a dict comprehension.
- Build a set of all the letters that are "below 100" in value.
- Build a list comprehension that converts a string like
"XII"to a list of integers[10, 1, 1]. - Sum them.
Sample · roman.py
ROMAN = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} # 1 — inverse INVERSE = {v: k for k, v in ROMAN.items()} # 2 — small letters small_letters = {ch for ch, v in ROMAN.items() if v < 100} # 3 — convert a roman string to numbers (naïve — no subtractive notation) text = "XII" nums = [ROMAN[ch] for ch in text] # 4 — sum print(f" Roman: {text}") print(f" Mapping: {nums}") print(f" Total: {sum(nums)}") # 12 print(f" Inverse: {INVERSE}") print(f" Small: {small_letters}") # {'I', 'V', 'X', 'L'}
Non-negotiables: dict comp for the inverse, set comp for the small letters, list comp for the conversion, sum to total. Naïve conversion only — real Roman has subtractive cases like "IV" that need extra logic.