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)
Output
{'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"])
Output
4.5
coffee

Unlike 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", ""))
Output
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)
Output
{'amount': 4.5, 'category': 'coffee', 'note': 'flat white'}
expense = {"amount": 4.50, "category": "coffee"}
expense["amount"] = 5.00
print(expense)
Output
{'amount': 5.0, 'category': 'coffee'}

del removes a key entirely:

expense = {"amount": 4.50, "category": "coffee", "note": ""}
del expense["note"]
print(expense)
Output
{'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)
Output
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)
Output
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)
Output
{'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)
Output
{'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)
Output
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}")
Output
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)
Output
4.5
coffee
flat white

When 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']}")
Output
coffee: 4.5
transit: 12.0

Notice 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.

  1. Build one entry dict for a $32.10 grocery purchase with the note "weekly shop", then print its "amount" and "category".
  2. Given expenses holding two entries (build your own), loop over it and print each one's category and amount as "category: amount".
  3. 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.
  4. 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"])
Output
32.1
groceries

2.

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']}")
Output
coffee: 4.5
groceries: 45.0

3.

entry = {"amount": 12.00, "category": "transit"}
print(entry.get("note", "(no note)"))
Output
(no note)

4.

entry = {"amount": 12.00, "category": "transit"}
entry.update({"note": "monthly pass"})
print(entry)
Output
{'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.