
Python Course
Dictionary Comprehensions in Python
In Lesson 21, list comprehensions let you build lists in a single expressive line. Python applies exactly the same idea to dictionaries. A dictionary comprehension creates, transforms, or filters a dictionary without writing a loop, an empty dict, and manual key assignments. The syntax is nearly identical — you just use curly braces and provide both a key and a value expression.
Dictionary comprehensions appear constantly in real Python codebases — API response processing, data normalisation, configuration building, and lookup table construction. This lesson covers every pattern you will encounter.
The Problem Dictionary Comprehensions Solve
To map each word in a list to its length, the traditional loop approach needs four lines:
# Traditional loop
words = ["apple", "banana", "cherry"]
lengths = {}
for w in words:
lengths[w] = len(w)
print(lengths)
# Dictionary comprehension — same result, one line
lengths = {w: len(w) for w in words}
print(lengths)
Both produce identical results. The comprehension reads naturally: "give me w: len(w) for each w in words."
Basic Syntax and Common Patterns
Structure: {key_expr: value_expr for item in iterable}. Both the key and value expressions are evaluated once per item.
# Map numbers to their squares
squares = {n: n ** 2 for n in range(1, 8)}
print(squares)
# Map numbers to their cubes
cubes = {n: n ** 3 for n in range(1, 6)}
print(cubes)
# Build a lookup: character → ASCII code
ascii_map = {c: ord(c) for c in "PYTHON"}
print(ascii_map)
# Build a lookup: user ID → username from a list of tuples
users = [(1, "alice"), (2, "bob"), (3, "carol")]
id_to_name = {uid: name for uid, name in users}
print(id_to_name)
- Curly braces
{}with a colon make it a dict — without a colon it would be a set comprehension. - Keys must be hashable — strings, numbers, and tuples work; lists and dicts do not.
- If the same key appears more than once, the last value wins — later iterations overwrite earlier ones.
Transforming an Existing Dictionary
Iterate directly over an existing dictionary using .items() to transform keys, values, or both — without touching the original.
costs = {"shirt": 12.00, "hat": 8.50, "bag": 22.00, "scarf": 6.00}
# Apply 40% markup to every price
retail = {item: round(price * 1.40, 2) for item, price in costs.items()}
print("Retail:", retail)
# Uppercase all keys
upper_keys = {k.upper(): v for k, v in costs.items()}
print("Upper:", upper_keys)
# Round all values to nearest pound
rounded = {k: round(v) for k, v in costs.items()}
print("Rounded:", rounded)
# Transform both key and value simultaneously
formatted = {k.title(): f"£{v:.2f}" for k, v in costs.items()}
print("Formatted:", formatted)
dict.items()yields(key, value)pairs — unpack both directly in theforclause.- You can transform the key, the value, or both in the same expression.
- The original dictionary is never modified — a new one is always returned.
Filtering with an if Clause
Add a trailing if condition to include only pairs that meet a test. Pairs where the condition is False are excluded from the new dictionary entirely.
scores = {"Alice": 88, "Bob": 53, "Carol": 76, "Dave": 45, "Eve": 92}
# Keep only students who passed (≥60)
passed = {name: score for name, score in scores.items() if score >= 60}
print("Passed:", passed)
# Keep only students who failed
failed = {name: score for name, score in scores.items() if score < 60}
print("Failed:", failed)
# Filter by key — keep only names starting with a vowel
vowel_names = {k: v for k, v in scores.items() if k[0].lower() in "aeiou"}
print("Vowel names:", vowel_names)
# Filter an inventory — keep only affordable in-stock items
inventory = {
"pen": {"price": 1.50, "stock": 200},
"desk": {"price": 350, "stock": 0},
"notebook": {"price": 4.99, "stock": 50},
"lamp": {"price": 24.99,"stock": 0},
"ruler": {"price": 0.99, "stock": 100},
}
available = {
item: info["price"]
for item, info in inventory.items()
if info["stock"] > 0 and info["price"] < 10
}
print("Affordable & in stock:", available)
- Structure with filter:
{k: v for k, v in d.items() if condition} - You can filter on the key, the value, or both — any valid boolean expression works.
- Spreading a comprehension over multiple lines (using parentheses or natural indentation) is fine when readability requires it.
Building a Dictionary from Two Lists
A very common pattern is pairing two parallel lists — one of keys and one of values — into a dictionary using zip(). Use the comprehension form when you need to transform the keys or values while building.
products = ["coffee", "tea", "juice", "water"]
prices = [3.50, 2.00, 4.25, 1.00 ]
# Basic pairing — comprehension or dict(zip()) both work
menu = {item: price for item, price in zip(products, prices)}
print("Menu:", menu)
# Comprehension adds power — transform while pairing
menu_formatted = {item.title(): f"${price:.2f}" for item, price in zip(products, prices)}
print("Formatted menu:", menu_formatted)
# Build from CSV header + data row
header = ["name", "dept", "salary"]
data = ["Priya", "Engineering", 90000 ]
record = {field: value for field, value in zip(header, data)}
print("Record:", record)
# Enumerate gives index + item — useful as a lookup
words = ["python", "data", "science"]
index_map = {word: i for i, word in enumerate(words)}
print("Index map:", index_map)
zip()stops at the shorter list if lengths differ — usezip_longestfromitertoolsto fill missing values.- The CSV header + data row pattern is used constantly when reading raw CSV files without
DictReader. enumerate()pairs each item with its index — very useful for building position lookup tables.
Swapping Keys and Values
Inverting a dictionary — turning keys into values and values into keys — is a one-liner with dict comprehensions. Useful for building reverse lookup tables.
# Invert a dictionary — swap keys and values
country_code = {"US": "United States", "CA": "Canada", "MX": "Mexico", "IN": "India"}
code_country = {v: k for k, v in country_code.items()}
print(code_country)
# Real use — build a reverse word index
word_index = {"python": 0, "data": 1, "science": 2}
index_word = {v: k for k, v in word_index.items()}
print(index_word)
# Warning: if values are not unique, only the LAST key survives
grades = {"Alice": "A", "Bob": "B", "Carol": "A"} # Alice and Carol share "A"
inverted = {v: k for k, v in grades.items()}
print(inverted) # "A" maps to Carol (Bob's "A" overwrites Alice's)
- Inversion works correctly only when all values are unique and hashable.
- When values are not unique, later iterations silently overwrite earlier ones — the last key wins.
- To preserve all keys for a shared value, use
defaultdict(list)and append rather than overwrite.
Conditional Value Assignment — Ternary in the Value
Use an inline if/else in the value expression to assign different values based on a condition. Every key is still included — the ternary only decides which value it gets.
scores = {"Alice": 88, "Bob": 53, "Carol": 76, "Dave": 45, "Eve": 92}
# Label each student as pass or fail
results = {name: "pass" if score >= 60 else "fail"
for name, score in scores.items()}
print(results)
# Assign a letter grade
grades = {
name: "A" if score >= 90 else ("B" if score >= 75 else ("C" if score >= 60 else "F"))
for name, score in scores.items()
}
print(grades)
# Apply tiered discount based on quantity
orders = {"pen": 50, "notebook": 8, "desk": 1, "chair": 3}
discounts = {item: 0.20 if qty >= 20 else (0.10 if qty >= 5 else 0.0)
for item, qty in orders.items()}
print(discounts)
- The ternary goes in the value position — every key is included, the ternary picks the value.
- This is different from a trailing
if, which removes pairs entirely. - Keep nested ternaries to two levels maximum — move complex logic to a helper function beyond that.
Grouping Data with Dictionary Comprehensions
Combining set() or multiple comprehensions lets you group and reorganise data — a task that normally requires loops and defaultdict.
# Build a frequency map — count occurrences of each item
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
freq = {word: words.count(word) for word in set(words)}
print("Frequency:", freq)
# Normalise a dictionary — scale all values to 0–1 range
raw = {"speed": 120, "accuracy": 95, "memory": 80}
max_val = max(raw.values())
normalised = {k: round(v / max_val, 3) for k, v in raw.items()}
print("Normalised:", normalised)
# Process a list of API records into a lookup dict
api_records = [
{"id": "u1", "name": "Priya", "active": True},
{"id": "u2", "name": "Kiran", "active": False},
{"id": "u3", "name": "Arjun", "active": True},
]
# Build lookup: id → name, active users only
active_lookup = {r["id"]: r["name"] for r in api_records if r["active"]}
print("Active users:", active_lookup)
- Using
set(words)as the iterable gives unique words to count — avoiding duplicate keys. - Normalisation is a standard data science operation — scaling raw values to a common 0–1 range.
- The API records pattern (list of dicts → keyed lookup) is one of the most frequent real-world uses of dict comprehensions.
Nested Dictionary Comprehensions
You can use a dict comprehension as the value expression inside another to build nested dictionaries in a single statement.
# Build a grade table: student → subject → default score
students = ["Alice", "Bob", "Carol"]
subjects = ["math", "science", "english"]
grade_table = {s: {sub: 0 for sub in subjects} for s in students}
for student, grades in grade_table.items():
print(f"{student}: {grades}")
# Build a multiplication table
times_table = {r: {c: r * c for c in range(1, 6)} for r in range(1, 4)}
for row, cols in times_table.items():
print(f"Row {row}: {cols}")
- The inner comprehension runs fresh for every iteration of the outer — each student gets an independent inner dictionary, not shared references.
- Beyond one level of nesting, a regular loop is usually clearer to read and debug.
Quick Reference Table
| Pattern | Syntax | What It Does |
|---|---|---|
| Basic build | {k: v for x in iterable} | Build a dict from any iterable |
| From dict | {k: v for k, v in d.items()} | Transform an existing dictionary |
| Filter | {k: v for k, v in d.items() if cond} | Keep only matching pairs |
| From two lists | {k: v for k, v in zip(a, b)} | Combine parallel lists |
| From enumerate | {w: i for i, w in enumerate(lst)} | Item → index lookup |
| Invert | {v: k for k, v in d.items()} | Swap keys and values |
| Ternary value | {k: a if cond else b for ...} | Conditional value assignment |
| Nested | {k: {ik: iv ...} for ...} | Build nested dictionaries |
Practice
What punctuation marks a dictionary comprehension apart from a list comprehension?
Which dictionary method yields both keys and values for iteration?
What built-in function pairs two parallel lists before passing them to a dict comprehension?
Write the comprehension syntax to invert a dictionary called d.
What happens when two keys share the same value in a dictionary being inverted?
In a dict comprehension, what happens if the same key is generated more than once?
Quick Quiz
What does {x: x ** 2 for x in range(1, 4)} produce?
Which correctly filters a dictionary to keep only pairs where the value is greater than 10?
What does {v: k for k, v in {"a": 1, "b": 2}.items()} produce?
Which of the following is a valid key type in a dictionary comprehension?
What is the difference between a trailing if and a ternary if/else in a dict comprehension?
Given records = [{"id":"u1","name":"Priya","active":True},{"id":"u2","name":"Kiran","active":False},{"id":"u3","name":"Arjun","active":True}], what does {r["id"]: r["name"] for r in records if r["active"]} return?