PYTHON / DICTIONARIES AND SETS
Iterating and comprehending dictionaries
Iterate a dict over keys, values, or key-value pairs, and build new dicts with comprehensions that filter, transform, or invert data.
What you will learn
- Know that `for k in d:` yields keys, and use d.items() to unpack pairs
- Write dict comprehensions with a key expression, a value expression, and a filter
- Invert a mapping with {v: k for k, v in d.items()} and predict key collisions
- Delete keys safely by iterating list(d) instead of the live dict
Understanding Iterating and comprehending dictionaries
A dict is iterable, and the thing it yields is its keys, not its pairs. That is why `for part in stock:` gives you strings, and why `" ".join(stock)` produces a list of key names. When you want the value too, you have two choices: index back into the dict inside the loop, or ask for pairs with `d.items()` and unpack them into two loop variables. The pair form is preferred because it looks the value up once during iteration instead of doing a second hash lookup per key.
A dict comprehension is a loop whose result is a dict, written as `{key_expr: value_expr for name in iterable}`. The two expressions before the `for` are evaluated on every pass, so the key side controls identity and the value side controls content, and they can transform independently: `{p.upper(): c * 2 for p, c in d.items()}` changes both. Adding `if cond` at the end skips items entirely, which is how you filter a dict without mutating the original. The result is always a brand new dict, so the source is untouched.
Because a comprehension writes into a fresh dict, the ordinary rule for assignment applies: writing the same key twice keeps the last value, silently. That makes inversion (`{v: k for k, v in d.items()}`) lossy whenever two keys share a value, and it makes key expressions like `name[0]` collapse entries that start with the same letter. Nothing raises an error, so the only signal is a shorter dict than you expected. Insertion order follows the order the source was iterated, so the surviving winner is always the last one seen.
stock = {"bolt": 250, "nut": 0, "washer": 48, "screw": 0, "rivet": 91}
print(" ".join(stock))
for part, count in stock.items():
status = "out" if count == 0 else "ok"
print(f"{part:<7}{count:>4} {status}")
in_stock = {part: count for part, count in stock.items() if count}
print(in_stock)
doubled = {part.upper(): count * 2 for part, count in in_stock.items()}
print(doubled)
first_letter = {part[0]: part for part in stock}
print(first_letter)Iterating a dict yields keys unless you ask for .items(), and a dict comprehension builds a new dict where a repeated key expression silently overwrites the earlier value.
Worked examples
Inverting and ranking
Swaps keys with values in a comprehension, then walks the pairs in value order with enumerate.
codes = {"US": 1, "FR": 33, "JP": 81, "BR": 55}
by_code = {number: country for country, number in codes.items()}
print(by_code[81])
for rank, (country, number) in enumerate(sorted(codes.items(), key=lambda pair: pair[1]), start=1):
print(rank, country, number)Example explained
Line 1`{number: country for country, number in codes.items()}` reverses the roles of the two unpacked names, which is all an inversion is.
Line 2`sorted(codes.items(), ...)` returns a list of tuples, so the dict itself is never reordered.
Line 3`key=lambda pair: pair[1]` sorts on the dialling code rather than the country abbreviation.
Line 4The loop target `(country, number)` needs parentheses because enumerate hands back a tuple whose second element is itself a tuple.
Deleting keys while looping
Shows the RuntimeError raised by resizing a dict mid-iteration and the list() snapshot that avoids it.
scores = {"ana": 91, "bo": 45, "cy": 78, "di": 32}
try:
for name, score in scores.items():
if score < 50:
del scores[name]
except RuntimeError as error:
print("RuntimeError:", error)
print(scores)
for name in list(scores):
if scores[name] < 50:
del scores[name]
print(scores)Example explained
Line 1Deleting "bo" succeeds, but the next request for a pair detects that the dict shrank and raises.
Line 2The dict is left half-processed: "bo" is gone, "di" was never reached.
Line 3`list(scores)` copies the keys first, so the loop iterates the copy while the dict underneath changes freely.
Line 4A filtering comprehension such as `{k: v for k, v in scores.items() if v >= 50}` is the alternative when you can rebind the name.
Building a record from parallel lists
Uses zip inside a comprehension so the value expression can convert types on the way in.
headers = ["id", "score", "name"]
row = ["7", "88", "Mira"]
record = {h: (int(v) if v.isdigit() else v) for h, v in zip(headers, row)}
print(record)
print({k: type(v).__name__ for k, v in record.items()})Example explained
Line 1`zip(headers, row)` yields pairs, so the comprehension unpacks them like it would unpack .items().
Line 2The conditional expression sits in the value slot, which is why `dict(zip(...))` alone cannot do this.
Line 3The second comprehension keeps every key and rewrites only the values, a common shape for auditing a dict.
Line 4`type(v).__name__` gives the plain class name instead of the `<class 'int'>` repr.
Important notes
Swapping the colon for a comma turns `{k: v for ...}` into a set comprehension of tuples, which is a silent change in result type rather than a syntax error.
Order in the resulting dict follows the order the source was iterated, so wrap the source in `sorted()` if you want a sorted dict rather than sorting afterwards.
Common mistakes
Writing `for key, value in d:` without `.items()`; Python tries to unpack each key, giving `ValueError: too many values to unpack (expected 2)` for most string keys, or silently splitting the characters if the keys happen to be two characters long.
Inverting a dict whose values repeat and assuming every entry survives; duplicate key expressions overwrite silently, so `{"a": 1, "b": 2, "c": 1}` inverts to a two-item dict with no warning.
Calling `del d[key]` or `d[new] = x` inside `for k in d:`, which aborts the loop with `RuntimeError: dictionary changed size during iteration` and leaves the dict partly modified.
Try it yourself
Change, predict, then run
Start from `counts = {"the": 12, "cat": 3, "sat": 3, "mat": 1, "on": 9}` and build one comprehension that keeps only words appearing more than twice, then invert that result and print both dicts along with their lengths to see which entry the inversion loses.
Open the Python workspaceCheck your understanding
Given `d = {"a": 1, "b": 2, "c": 1}`, what does `{v: k for k, v in d.items()}` produce?
- {1: 'c', 2: 'b'}
- {1: 'a', 2: 'b'}
- {1: ['a', 'c'], 2: ['b']}
- A KeyError, because the key 1 is produced twice
Show answer
Each pass performs a plain assignment into the new dict, so the second time key 1 is written it overwrites the earlier value; "c" is iterated after "a", so 'c' wins. `{1: 'a', 2: 'b'}` is tempting if you assume the first occurrence is kept, but dict assignment never protects an existing key, and duplicates never raise.