086. Dict Comprehensions
Build dictionaries concisely from iterables
086. Dict Comprehensions
He has a list of processes and frequently asks “what is the RSS of process X?”. Linear search through the list is slow.
He builds a lookup dict with a dict comprehension:
processes = [
{"name": "chrome", "pid": 100, "rss_mb": 620},
{"name": "python", "pid": 101, "rss_mb": 85},
{"name": "slack", "pid": 102, "rss_mb": 310},
]
# Build a name → rss_mb lookup in one line
rss_by_name = {p["name"]: p["rss_mb"] for p in processes}
# → {"chrome": 620, "python": 85, "slack": 310}
# Now lookup is O(1)
rss_by_name["chrome"] # → 620
# Filter: only processes using > 100 MB
heavy = {p["name"]: p["rss_mb"] for p in processes if p["rss_mb"] > 100}
# → {"chrome": 620, "slack": 310}💡 Fun fact: Dict comprehensions were added in Python 2.7 / Python 3.0, several years after list comprehensions. Before that, the idiomatic way to build a dict from a list was dict((k, v) for k, v in pairs) using dict() with a generator. The {k: v for ...} syntax is purely syntactic sugar but dramatically more readable.
⚠️ Watch out: When using invert_dict — swapping keys and values — if the original dict has duplicate values, the inversion silently drops all but the last mapping for each value. For example, {"a": 1, "b": 1} inverted gives {1: "b"}, and "a" disappears with no error. Always verify uniqueness before inverting.
🤔 Think about it: A dict comprehension {p["name"]: p["rss_mb"] for p in processes} builds a lookup from O(n) linear search to O(1) lookup. But it also means the dict can become stale if the process list changes. How do real monitoring tools like psutil handle the problem of keeping derived data structures in sync with their source?
Learning objectives
- Build dictionaries with dict comprehensions
- Filter key-value pairs with conditions
- Swap dict keys and values with a comprehension
Key concepts
- dict comprehension
- key-value transformation
- filtering
Try it
Concept detail
Dict comprehension: {key_expr: val_expr for variable in iterable}
{w: len(w) for w in words}
# → {"cat": 3, "dog": 3, "elephant": 8}With filter: {k: v for k, v in d.items() if v > 0} # → only key-value pairs where value is positive
Inverting a dict (swap keys and values): {v: k for k, v in d.items()} # works only when values are unique and hashable
From two parallel lists: {k: v for k, v in zip(keys, values)}
WHY dict comprehensions:
- Building a lookup table from a list is a very common operation
- One-liner is faster and more readable than a 4-line loop
- Filtering during construction: no second pass needed
Set comprehension (just unique values, no mapping): {x for x in items} # set of unique items {p[“name”] for p in procs} # set of unique process names
Nested values require a regular loop when the logic gets complex.
Solution
def word_lengths(words):
return {word: len(word) for word in words}
def invert_dict(d):
return {v: k for k, v in d.items()}
def filter_scores(scores, min_score):
return {name: score for name, score in scores.items() if score >= min_score}Tests
def test_word_lengths():
result = word_lengths(["cat", "elephant", "ox"])
assert result["cat"] == 3
assert result["elephant"] == 8
assert result["ox"] == 2
def test_word_lengths_empty():
assert word_lengths([]) == {}
def test_invert_dict():
d = {"a": 1, "b": 2, "c": 3}
inv = invert_dict(d)
assert inv[1] == "a"
assert inv[2] == "b"
assert inv[3] == "c"
def test_invert_is_not_copy():
d = {"x": 10, "y": 20}
inv = invert_dict(d)
# inverted: keys are 10 and 20, values are 'x' and 'y'
assert 10 in inv
assert "x" not in inv
def test_filter_scores_pass():
scores = {"Alice": 90, "Bob": 75, "Carol": 85}
result = filter_scores(scores, 80)
assert "Alice" in result
assert "Carol" in result
assert "Bob" not in result
def test_filter_scores_none_pass():
scores = {"X": 50, "Y": 60}
assert filter_scores(scores, 70) == {}
def test_filter_scores_all_pass():
scores = {"A": 100, "B": 90}
result = filter_scores(scores, 80)
assert len(result) == 2