Chapter 6
Dictionaries
A list of bare amounts, [4.50, 12.00, 32.10], has a real limit: it can't
say what any of those amounts were for. This chapter's tool fixes that. A
dictionary holds named fields instead of a plain sequence, and from here
on it's how this book represents one recorded expense.
Making a dictionary
A dict literal is written in curly braces, as key: value pairs separated by
commas:
expense = {"amount": 4.50, "category": "coffee"}
print(expense)
{'amount': 4.5, 'category': 'coffee'}Get a value back out by its key, in square brackets:
expense = {"amount": 4.50, "category": "coffee"}
print(expense["amount"])
print(expense["category"])
4.5
coffeeUnlike a list, a dict isn't ordered by position, it's organized by name. There's no "first item" the way there is in a list; you always ask for a value by its key.
Asking for a key that isn't there raises an error:
expense = {"amount": 4.50, "category": "coffee"}
print(expense["note"])
That's KeyError: 'note'. .get() avoids the crash and lets you supply a
default:
expense = {"amount": 4.50, "category": "coffee"}
print(expense.get("note"))
print(expense.get("note", ""))
None
.get() without a default returns None, Python's value for "nothing
here," when the key is missing; that's the first line. The second line is
empty, because "" (an empty string) was the fallback you asked for
instead.
Adding, changing, and removing keys
Assign to a key to add it if it's missing or change it if it's there:
expense = {"amount": 4.50, "category": "coffee"}
expense["note"] = "flat white"
print(expense)
{'amount': 4.5, 'category': 'coffee', 'note': 'flat white'}expense = {"amount": 4.50, "category": "coffee"}
expense["amount"] = 5.00
print(expense)
{'amount': 5.0, 'category': 'coffee'}del removes a key entirely:
expense = {"amount": 4.50, "category": "coffee", "note": ""}
del expense["note"]
print(expense)
{'amount': 4.5, 'category': 'coffee'}in checks whether a key exists, without raising:
expense = {"amount": 4.50, "category": "coffee"}
print("category" in expense)
print("note" in expense)
True
False.pop() removes a key and returns its value in one step, the dict
equivalent of a list's .pop() from Chapter 5:
expense = {"amount": 4.50, "category": "coffee", "note": "flat white"}
note = expense.pop("note")
print(note)
print(expense)
flat white
{'amount': 4.5, 'category': 'coffee'}.update() merges another dict's keys into this one, adding new keys and
overwriting any that already exist:
expense = {"amount": 4.50, "category": "coffee"}
expense.update({"category": "beverage", "note": "flat white"})
print(expense)
{'amount': 4.5, 'category': 'beverage', 'note': 'flat white'}"category" already existed and got overwritten; "note" was new and got
added. The | operator does the same merge without changing either
original dict, building a fresh one instead:
defaults = {"category": "uncategorized", "note": ""}
expense = {"amount": 4.50, "category": "coffee"}
merged = defaults | expense
print(merged)
{'category': 'coffee', 'note': '', 'amount': 4.5}Where both dicts share a key, the one on the right wins, which is why
merged["category"] ended up "coffee", not "uncategorized": think of it
as "defaults, then whatever the right side overrides."
Looping over a dict
Looping over a dict directly gives you its keys:
expense = {"amount": 4.50, "category": "coffee"}
for key in expense:
print(key)
amount
category.items() gives you both the key and the value together, which is what
you'll want almost every time you loop over a dict:
expense = {"amount": 4.50, "category": "coffee"}
for key, value in expense.items():
print(f"{key}: {value}")
amount: 4.5
category: coffee.values() gives you just the values, useful when the keys don't matter for
what you're doing:
expense = {"amount": 4.50, "category": "coffee", "note": "flat white"}
for value in expense.values():
print(value)
4.5
coffee
flat whiteWhen to use a dict instead of a list
A list is right for an ordered group of similar, interchangeable values, like
amounts. A dict is right the moment each value has a name and the names
matter, like an amount, a category, and a note that together describe one
expense. If you find yourself writing amounts[0] for the price and
amounts[1] for the category and just remembering which position means
what, that's a sign you want a dict instead: expense["amount"] and
expense["category"] say what they mean without you having to remember an
order.
| List | Dict | |
|---|---|---|
| Access by | position (amounts[0]) |
name (expense["amount"]) |
| Order | keeps insertion order, meaningfully | keeps insertion order, but you don't look things up by it |
| Good for | a group of similar, interchangeable values | a group of named fields describing one thing |
| This book's example | amounts, a plain list of numbers |
expense, one entry with named fields |
Nothing stops a dict from holding a list as one of its values, or a list
from holding dicts, the way expenses does starting in the next section.
Data in real programs nests like this constantly: a dict of a person's
details might hold a list of their orders, and each order might itself be a
dict. You don't need anything new to handle it, just the same indexing and
looping rules, one level at a time.
This book's entry shape
From here to the end of the book, one recorded expense is a dict with exactly these three keys, and this shape doesn't change again:
{"amount": 4.50, "category": "coffee", "note": "flat white"}
"amount" is always a number, "category" and "note" are always strings
("note" can be empty, "", but it's always present). This book calls one
of these an entry. A group of entries is a plain list of them, bound to
the name expenses:
expenses = [
{"amount": 4.50, "category": "coffee", "note": "flat white"},
{"amount": 12.00, "category": "transit", "note": "monthly pass"},
]
for entry in expenses:
print(f"{entry['category']}: {entry['amount']}")
coffee: 4.5
transit: 12.0Notice the quotes inside the f-string, entry['category'], are single quotes
while the f-string itself uses double quotes. That's on purpose: Python
needs a way to tell where the outer string ends and the inner one begins, so
mixing quote styles like this avoids a conflict.
This is the shape every chapter from here on builds on. Chapter 7 writes functions that take a list of entries and do something with it; Chapter 9 reads and writes entries from a real file.
Practice
Try each of these before you read the solution under it.
- Build one entry dict for a $32.10 grocery purchase with the note
"weekly shop", then print its"amount"and"category". - Given
expensesholding two entries (build your own), loop over it and print each one's category and amount as"category: amount". - Given an entry that might or might not have a
"note"key, use.get()to print its note, or"(no note)"if there isn't one. - Given
entry = {"amount": 12.00, "category": "transit"}, use.update()to add"note": "monthly pass"to it, then print the result.
Solutions
1.
entry = {"amount": 32.10, "category": "groceries", "note": "weekly shop"}
print(entry["amount"])
print(entry["category"])
32.1
groceries2.
expenses = [
{"amount": 4.50, "category": "coffee", "note": ""},
{"amount": 45.00, "category": "groceries", "note": "big shop"},
]
for entry in expenses:
print(f"{entry['category']}: {entry['amount']}")
coffee: 4.5
groceries: 45.03.
entry = {"amount": 12.00, "category": "transit"}
print(entry.get("note", "(no note)"))
(no note)4.
entry = {"amount": 12.00, "category": "transit"}
entry.update({"note": "monthly pass"})
print(entry)
{'amount': 12.0, 'category': 'transit', 'note': 'monthly pass'}Where this leaves you
You can build and use a dictionary, choose between a dict and a list for a
given job, and you know this book's entry shape: three fixed keys,
"amount", "category", "note", that stay exactly this way for the rest
of the book. Chapter 7 writes real functions around it.