Dictionaries
Map keys to values with dicts, look things up safely, merge dicts with the pipe operator, and preview the new immutable frozendict.
What you will learn
- Create dicts and read, add, update and delete entries
- Look up keys safely with get, setdefault and in
- Iterate over keys, values and items
- Merge dicts with the | and |= operators
On this page
A list finds things by position. A dictionary finds them by name: look up "Ada" and get her age, look up a word and get its definition. Dicts are among the most used structures in Python, and the language itself leans on them everywhere, from keyword arguments to object attributes. The shared fundamentals lesson on maps and sets explains the idea in the abstract. Here is how Python does it.
Creating and using a dict
Curly braces with key: value pairs make a dict. Assigning to a key either adds a new entry or replaces an existing one. Each key appears once.
ages = {"Ada": 36, "Alan": 41}ages["Grace"] = 85ages["Ada"] = 37print(ages, len(ages))print(ages["Alan"]){'Ada': 37, 'Alan': 41, 'Grace': 85} 341Keys must be hashable, which in practice means immutable things such as strings, numbers and tuples. Values can be anything.
Safe lookups
ages["Linus"] raises KeyError when the key is missing:
ages = {"Ada": 37}print(ages["Linus"])KeyError: 'Linus'That is right when a missing key is a bug, but often it is just a case to handle. Three tools let you look without crashing:
d.get(key)returnsNoneif the key is absent, andd.get(key, default)returns your default instead. It never changes the dict.key in dtests for a key.d.setdefault(key, default)returns the value if the key exists, and otherwise stores the default and returns it.
ages = {"Ada": 37, "Alan": 41}print(ages.get("Linus"))print(ages.get("Linus", 0))print("Ada" in ages, "Linus" in ages)None0True Falsesetdefault makes the grouping idiom a single line: fetch the list for this letter (creating an empty one the first time) and append to it.
groups = {}for word in ["apple", "avocado", "banana", "blueberry", "cherry"]: groups.setdefault(word[0], []).append(word)print(groups){'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}To remove entries, use del d[key] (raises KeyError if absent) or d.pop(key), which also returns the value. pop(key, default) stays quiet when the key is missing.
ages = {"Ada": 37, "Alan": 41, "Grace": 85}del ages["Alan"]print(ages.pop("Grace"), ages)print(ages.pop("Nobody", "not there"))85 {'Ada': 37}not thereIterating
A dict remembers the order in which keys were first inserted, and loops follow that order. for key in d visits keys, d.values() gives the values, and d.items() gives (key, value) pairs, which you normally unpack in the loop header.
stock = {"pens": 12, "ink": 3, "paper": 40}
for key in stock: print(key, end=" ")print()
for item, count in stock.items(): print(f"{item}: {count}")pens ink paperpens: 12ink: 3paper: 40These “views” show the dict’s current contents. Wrap them in list(...) when you want a snapshot. reversed(stock) walks the keys from last to first.
stock = {"pens": 12, "ink": 3, "paper": 40}print(list(stock.keys()), list(stock.values()))
for key in reversed(stock): print(key, end=" ")print()['pens', 'ink', 'paper'] [12, 3, 40]paper ink pensDo not add or delete keys while looping over the dict, because Python raises a RuntimeError.
Merging dicts
Requires version 3.9 or later a | b builds a new dict containing the entries of both. When a key appears in both, the right-hand value wins, so layering user settings over defaults is one expression.
defaults = {"theme": "light", "font": 12, "lang": "en"}user = {"theme": "dark", "font": 14}
merged = defaults | userprint(merged)print(defaults){'theme': 'dark', 'font': 14, 'lang': 'en'}{'theme': 'light', 'font': 12, 'lang': 'en'}a |= b updates a in place, like a.update(b):
defaults = {"theme": "light", "font": 12, "lang": "en"}defaults |= {"lang": "fr"}print(defaults)print({"a": 1} | {"a": 2}){'theme': 'light', 'font': 12, 'lang': 'fr'}{'a': 2}These operators arrived in Python 3.9 (PEP 584). On older versions, {**a, **b} does the same job as a | b; the double star spreads each dict’s entries into the new one, just as * spreads a list.
defaults = {"theme": "light", "font": 12}user = {"theme": "dark"}print({**defaults, **user}){'theme': 'dark', 'font': 12}frozendict, an immutable dict
Requires version 3.15 or later Python 3.15 added a built-in frozendict (PEP 814). It looks like a dict but cannot be changed after it is created, which makes it hashable whenever its keys and values are. This section needs Python 3.15; on 3.14 or earlier the name does not exist and you get a NameError.
config = frozendict(host="localhost", port=8080)print(config)print(config["port"], config.get("debug", False))frozendict({'host': 'localhost', 'port': 8080})8080 FalseAssigning to it fails:
config = frozendict(host="localhost", port=8080)config["port"] = 9090TypeError: 'frozendict' object does not support item assignmentOrder does not affect equality or hashing, and a frozendict can be a dict key, which an ordinary dict cannot:
config = frozendict(host="localhost", port=8080)other = frozendict(port=8080, host="localhost")print(config == other, hash(config) == hash(other))
cache = {config: "connection #1"}print(cache[other])True Trueconnection #1Two more facts to remember. A frozendict is not a subclass of dict, so isinstance(x, dict) is false for it. And since it cannot be modified, “changing” one means building a new one.
config = frozendict(host="localhost", port=8080)print(isinstance(config, dict))
updated = frozendict({**config, "port": 9090})print(updated)print(config)Falsefrozendict({'host': 'localhost', 'port': 9090})frozendict({'host': 'localhost', 'port': 8080})For code that must run on older versions, stick to a plain dict and simply do not mutate it.
Common pitfalls
KeyErrorfromd[key]. Useget,in, orsetdefaultwhen the key may be missing.- Unhashable keys. A list or another dict cannot be a key. Convert a list to a tuple first.
- Mutating while iterating. Adding or removing keys inside
for k in draisesRuntimeError. Loop overlist(d)instead. - Expecting
|to modify its operands.a | breturns a new dict. Use|=to updatea. - Mixing up
{}. An empty{}is an empty dict, not an empty set. Useset()for a set. - Assuming
getstores the default. It does not. Onlysetdefaultstores it.
Predict the four lines this prints, then run the file. Input: none. Expected output: four lines.
stock = {"pens": 12, "ink": 3}print(stock.get("paper", 0))print(stock.pop("ink"))print(stock)print("ink" in stock)Show solution
03{'pens': 12}Falseget returns the default 0 for the missing key and does not add it. pop removes ink and hands back its value, 3. After that the dict only has pens, so "ink" in stock is false.
Count how often each word occurs in a text, ignoring case. Print every word that occurs more than once, in alphabetical order, with its count, and then print the number of distinct words.
Input: the two lines of text below. Expected output:
dog 2fox 2the 412 distinct wordstext = """the quick brown fox jumps over the lazy dogThe dog barks and the fox runs away"""Show solution
text = """the quick brown fox jumps over the lazy dogThe dog barks and the fox runs away"""
counts = {}for word in text.lower().split(): counts[word] = counts.get(word, 0) + 1
for word in sorted(counts): if counts[word] > 1: print(word, counts[word])print(len(counts), "distinct words")dog 2fox 2the 412 distinct wordscounts.get(word, 0) + 1 reads “the current count, or zero if we have not seen it”. Looping over sorted(counts) visits the keys in alphabetical order. max(counts, key=counts.get) would give you the single most common word.
Swap keys and values in colors = {"apple": "red", "cherry": "red", "banana": "yellow", "lime": "green"}. Several keys may share a value, so each color should map to a list of fruits. Print the result.
Input: the colors dict. Expected output: {'red': ['apple', 'cherry'], 'yellow': ['banana'], 'green': ['lime']}.
Show solution
colors = {"apple": "red", "cherry": "red", "banana": "yellow", "lime": "green"}
inverted = {}for fruit, color in colors.items(): inverted.setdefault(color, []).append(fruit)print(inverted){'red': ['apple', 'cherry'], 'yellow': ['banana'], 'green': ['lime']}setdefault creates the empty list the first time a color appears, so one line handles both the first and later fruits.
Summary
- A dict maps hashable keys to values and iterates in insertion order. Use
get,inandsetdefaultfor safe lookups. - Requires version 3.9 or later
a | bmerges into a new dict anda |= bupdates in place. The right side wins on clashes. - Requires version 3.15 or later
frozendictis an immutable, hashable mapping that is not adictsubclass.
Next: sets.