Dictionaries and Sets in Python
Key-value lookup with dictionaries: creating, reading safely, updating, iterating, counting and grouping, nested records and comprehensions. Then sets for uniqueness, fast membership and set algebra.
Dictionaries: looking things up by key
A list answers "what is at position 3?". Often you want to ask "what is the phone number for Ada?" instead. A dictionary (dict) stores key → value pairs and finds a value by its key directly, without scanning, however large the dictionary is.
Write a dictionary with curly braces and key: value pairs. Read a value with square brackets: ages["Ada"]. Keys are unique: assigning to an existing key replaces its value.
Looking up a key that does not exist raises KeyError. When a key may be missing, use get(key), which returns None, or get(key, default), which returns your default.
A mutable mapping from unique, hashable keys to values. Lookup, insertion and deletion by key take constant time on average, and iteration follows insertion order.
`in` on a dictionary checks keys, not values. Use `value in d.values()` to search the values (which is a slow scan).
1capitals = {"France": "Paris", "Japan": "Tokyo", "Kenya": "Nairobi"}2print(capitals["Japan"])3print(len(capitals))4print(capitals.get("Peru"))5print(capitals.get("Peru", "unknown"))6print("Kenya" in capitals, "Nairobi" in capitals)
Tokyo 3 None unknown True False
Adding, changing and removing entries
Dictionaries are mutable:
d[key] = valueadds a new pair or replaces the value of an existing key.d.update(other)copies every pair from another dictionary, overwriting matching keys.d.pop(key)removes a key and returns its value;d.pop(key, default)does not fail when the key is missing.del d[key]removes a key (and raisesKeyErrorif it is absent).
Since Python 3.7, dictionaries remember insertion order: iterating gives keys in the order they were first added. Replacing a value keeps the key's position.
1stock = {"apples": 12, "pears": 4}2stock["plums"] = 93stock["apples"] -= 54stock.update({"pears": 10, "figs": 3})5print(stock)6sold_out = stock.pop("plums")7print(sold_out, stock)8print(stock.pop("kiwis", 0))9del stock["figs"]10print(stock)
{'apples': 7, 'pears': 10, 'plums': 9, 'figs': 3}
9 {'apples': 7, 'pears': 10, 'figs': 3}
0
{'apples': 7, 'pears': 10}Looping over a dictionary
Three views give you what you need to loop over:
d.keys(): the keys (also what you get fromfor key in d)d.values(): the valuesd.items():(key, value)pairs, best unpacked directly in theforline
To process entries in a particular order, sort the items: sorted(d.items(), key=lambda kv: kv[1]) sorts by value. Do not add or remove keys while looping over a dictionary; Python raises RuntimeError. Loop over list(d) if you need to delete as you go.
1prices = {"coffee": 3.2, "tea": 2.5, "cake": 4.0}2for item in prices:3 print(item, end=" ")4print()5print(sum(prices.values()))6for item, price in prices.items():7 print(f"{item:<8}{price:>6.2f}")8cheapest = min(prices.items(), key=lambda kv: kv[1])9print("Cheapest:", cheapest)
coffee tea cake
9.7
coffee 3.20
tea 2.50
cake 4.00
Cheapest: ('tea', 2.5)Counting and grouping
Two patterns account for a large share of real dictionary use.
Counting: start each key at 0 the first time you see it. counts[x] = counts.get(x, 0) + 1 does that in one line.
Grouping: collect items into a list per key. groups.setdefault(key, []).append(item) creates the empty list the first time and appends every time.
The standard library packages both patterns: collections.Counter counts an iterable in one call and collections.defaultdict(list) creates missing lists automatically. Learn the manual versions first, since they work everywhere and make the idea clear.
1words = "the cat sat on the mat with the hat".split()23counts = {}4for w in words:5 counts[w] = counts.get(w, 0) + 16print(counts)78by_letter = {}9for w in words:10 by_letter.setdefault(w[0], []).append(w)11print(by_letter)1213from collections import Counter14print(Counter(words).most_common(2))
{'the': 3, 'cat': 1, 'sat': 1, 'on': 1, 'mat': 1, 'with': 1, 'hat': 1}
{'t': ['the', 'the', 'the'], 'c': ['cat'], 's': ['sat'], 'o': ['on'], 'm': ['mat'], 'w': ['with'], 'h': ['hat']}
[('the', 3), ('cat', 1)]Records and nested data
A dictionary is a natural record: named fields instead of positions. A list of such dictionaries is a table, and dictionaries can contain lists and other dictionaries. This is exactly the shape of JSON data that web services send and receive, which the files lesson uses.
Read nested values one level at a time: user["address"]["city"]. When an intermediate level might be missing, chain get calls with an empty dictionary as the default.
1students = [2 {"name": "Ada", "scores": [91, 88], "address": {"city": "London"}},3 {"name": "Alan", "scores": [79, 85], "address": {}},4]5for s in students:6 average = sum(s["scores"]) / len(s["scores"])7 city = s["address"].get("city", "unknown")8 print(f"{s['name']}: {average:.1f} ({city})")910best = max(students, key=lambda s: sum(s["scores"]))11print("Best:", best["name"])
Ada: 89.5 (London) Alan: 82.0 (unknown) Best: Ada
What can be a key
Dictionary lookup works by hashing: Python turns the key into a number that points to where the value is stored. That only works if the key never changes, so keys must be hashable, which in practice means immutable: numbers, strings, booleans, None and tuples of these. Lists and dictionaries cannot be keys and raise TypeError: unhashable type.
Values have no restrictions; they can be anything, including lists and other dictionaries.
1board = {}2board[(0, 0)] = "X"3board[(1, 2)] = "O"4print(board.get((1, 2)), board.get((2, 2), "."))5print(hash("abc") == hash("abc"))
O . True
Dictionary comprehensions
A dictionary comprehension builds a dictionary in one expression: {key_expr: value_expr for item in iterable if condition}. It is handy for transforming or filtering an existing dictionary, or for building a lookup table from a list.
Inverting a dictionary ({v: k for k, v in d.items()}) only works when the values are unique; otherwise later keys overwrite earlier ones.
1names = ["ada", "grace", "linus"]2lengths = {n: len(n) for n in names}3print(lengths)45prices = {"tea": 2.5, "cake": 4.0, "water": 1.0}6print({k: v for k, v in prices.items() if v >= 2})7print({k: round(v * 1.2, 2) for k, v in prices.items()})8print({v: k for k, v in lengths.items()})
{'ada': 3, 'grace': 5, 'linus': 5}
{'tea': 2.5, 'cake': 4.0}
{'tea': 3.0, 'cake': 4.8, 'water': 1.2}
{3: 'ada', 5: 'linus'}Sets: unique values and fast membership
A set is an unordered collection of unique, hashable values, written with curly braces: {"python", "sql"}. (An empty set is set(), because {} is an empty dictionary.)
Sets are the right tool for three jobs:
- Removing duplicates:
set(items). - Fast membership tests:
x in some_settakes constant time on average, whilex in some_listchecks every item. - Set algebra:
a | b(union),a & b(intersection),a - b(difference) anda ^ b(in exactly one).
Sets have no order and no indexes. When you print them or need a stable order, convert with sorted(s).
Never rely on the order in which a set prints or iterates; it can differ between runs and Python versions. Sort first when order matters.
1tags = ["python", "web", "python", "sql", "web"]2unique = set(tags)3print(len(tags), len(unique))4print(sorted(unique))56team_a = {"python", "sql", "go"}7team_b = {"python", "rust", "sql"}8print(sorted(team_a | team_b))9print(sorted(team_a & team_b))10print(sorted(team_a - team_b))11print(sorted(team_a ^ team_b))1213unique.add("css")14unique.discard("web")15print("css" in unique, "web" in unique)
5 3 ['python', 'sql', 'web'] ['go', 'python', 'rust', 'sql'] ['python', 'sql'] ['go'] ['go', 'rust'] True False
Choosing a collection
Pick the structure by the question your program asks most often.
list, tuple, dict or set?
| Structure | Ordered | Mutable | Duplicates | Best for |
|---|---|---|---|---|
| list | Yes | Yes | Yes | A sequence of items you add to, sort and loop over |
| tuple | Yes | No | Yes | Fixed records, multiple return values, keys |
| dict | Insertion order | Yes | Unique keys | Looking up a value by a key, counting, grouping |
| set | No | Yes | No | Uniqueness, fast membership, set algebra |
- •Use d.get(key, default) when a key may be missing.
- •Iterate with for key, value in d.items().
- •Count with counts.get(x, 0) + 1 and group with setdefault(key, []).append(x).
- •Sets remove duplicates and make membership checks fast, but have no order.
Check your understanding
What does `print({"a": 1}.get("b", 0))` print?
Practice
Count word frequencies with a dictionary, compare two lists with set operations, and merge nested configuration dictionaries recursively.
Word Frequency Counter
Reveal Solution & AnalysisHide Solution
Common Elements
Reveal Solution & AnalysisHide Solution
Dictionary Deep Merge
Reveal Solution & AnalysisHide Solution
Quick Syntax & Reference Sheet
| Concept / Rule | Syntax / Usage |
|---|---|
| Create / read | d = {"a": 1}; d["a"]; d.get("b", 0) |
| Add / update / remove | d["b"] = 2; d.update(other); d.pop("a") |
| Loop over pairs | for key, value in d.items(): |
| Count | counts[x] = counts.get(x, 0) + 1 |
| Group | groups.setdefault(k, []).append(item) |
| Dict comprehension | {k: v * 2 for k, v in d.items()} |
| Set operations | a | b, a & b, a - b, a ^ b |
| Empty set | s = set() |
Common Pitfalls & Developer Mistakes
Reading a missing key with d[key]
Why it happens: It raises KeyError.
Writing {} for an empty set
Why it happens: {} is an empty dictionary.
Changing a dictionary's keys while iterating over it
Why it happens: Python raises RuntimeError: dictionary changed size during iteration.
Depending on set order
Why it happens: Sets are unordered; string order can differ between runs.