← Home

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

Resources