PYTH-101 · Lesson 9 of 12 · 23 min

Dictionaries and sets

▶ Video coming soon A video version of this lesson is on the way.

Lists keep items in order and find them by position. Much business data is better found by name. To check the stock of RC-104, Theo does not want to remember that it is the fourth coffee; he wants to look up RC-104 directly. Python's dictionary does exactly that: it stores pairs of keys and values and finds any value by its key almost instantly, however large the dictionary grows. This lesson covers dictionaries in depth, and then sets, which store unique values and answer questions such as which coffees were sold both online and in the cafe.

Dictionaries are among the most useful tools in Python. Once you are comfortable with them, many programs that would need loops and searches become a few lines of lookups. By the end of the lesson Ridgeline's coffee list will live in a dictionary keyed by stock code, which is how the capstone in lesson 12 stores it.

Creating and using dictionaries

A dictionary is written in curly braces as key: value pairs separated by commas. Keys are usually strings or numbers and must be unique; values can be anything. You look a value up by putting its key in square brackets, and you add or change an entry by assigning to a key. Unlike a list, a dictionary has no positions: d[0] means the entry whose key is 0, not the first entry.

A dictionary of stock levels keyed by stock code
stock = {"RC-101": 42, "RC-102": 15, "RC-103": 27,
         "RC-104": 9, "RC-105": 12, "RC-106": 30}
print(stock["RC-104"])
stock["RC-104"] += 30        # a fresh batch
stock["RC-107"] = 24         # a new coffee
print(stock)
print(len(stock), "coffees")
print("RC-108" in stock)
Output
9
{'RC-101': 42, 'RC-102': 15, 'RC-103': 27, 'RC-104': 39, 'RC-105': 12, 'RC-106': 30, 'RC-107': 24}
7 coffees
False
A dictionary drawn as two columns. On the left, keys RC-101 to RC-106; on the right, their values 42, 15, 27, 9, 12 and 30, joined by arrows. A lookup stock["RC-104"] follows the arrow from RC-104 straight to 9 without passing the other entries. Below, a list of the same six numbers is searched one cell at a time from the left until the fourth cell is reached.
A dictionary goes straight from a key to its value; a list must be searched or indexed by position

Since Python 3.7, dictionaries remember the order in which keys were added, so printing or looping over one gives the entries in insertion order. That is convenient for reports, but the purpose of a dictionary is lookup by key, not order. The in operator tests whether a key is present; it checks keys, not values.

Looking up a key that does not exist raises a KeyError. When a missing key is a normal possibility, use the get method, which returns None, or a default you choose, instead of failing. get is the safe way to look things up when you are not sure the key is there.

Avoiding KeyError with get and with in
stock = {"RC-101": 42, "RC-104": 9}
print(stock.get("RC-104"))
print(stock.get("RC-999"))
print(stock.get("RC-999", 0))
code = "RC-999"
if code in stock:
    print(code, "has", stock[code], "bags")
else:
    print(code, "is not a Ridgeline coffee")
Output
9
None
0
RC-999 is not a Ridgeline coffee

To remove an entry, use del d[key], or d.pop(key), which also returns the value. The update method merges another dictionary into this one, adding new keys and overwriting existing ones, which is handy for applying a batch of changes at once.

Looping over dictionaries

A for loop over a dictionary goes through its keys. The methods keys, values and items give views of the keys, the values and the key-value pairs. Looping over items with two loop variables is the most common pattern, because it gives you both parts of each entry at once.

Looping over keys, values and items
stock = {"RC-101": 42, "RC-102": 15, "RC-103": 27,
         "RC-104": 9, "RC-105": 12, "RC-106": 30}
for code in stock:
    print(code, end=" ")
print()
print("Total bags:", sum(stock.values()))
for code, bags in stock.items():
    if bags < 15:
        print(f"{code} is low: {bags} bags")
Output
RC-101 RC-102 RC-103 RC-104 RC-105 RC-106 
Total bags: 135
RC-104 is low: 9 bags
RC-105 is low: 12 bags

To process a dictionary in another order, sort its items. sorted(stock.items()) sorts by key; sorting by value needs a key function that picks out the second element of each pair, written key=lambda pair: pair[1]. lambda creates a tiny unnamed function in place; you saw it in lesson 8's exercise and PYTH-102 explains it fully.

Sorting a dictionary by its values
stock = {"RC-101": 42, "RC-102": 15, "RC-103": 27,
         "RC-104": 9, "RC-105": 12, "RC-106": 30}
for code, bags in sorted(stock.items(), key=lambda pair: pair[1]):
    print(code, bags)
print("Fullest shelf:", max(stock, key=stock.get))
Output
RC-104 9
RC-105 12
RC-102 15
RC-103 27
RC-106 30
RC-101 42
Fullest shelf: RC-101

Counting with a dictionary

One of the classic uses of a dictionary is counting how often each thing occurs. Start with an empty dictionary; for each item, add 1 to its count, using get with a default of 0 so the first occurrence works. After the loop the dictionary holds every distinct item and its count. Anita uses exactly this to see which drinks sold best in a shift.

Counting drinks with a dictionary and a text bar chart
shift = ["latte", "drip", "latte", "mocha", "drip", "latte",
         "tea", "latte", "cappuccino", "drip"]
counts = {}
for drink in shift:
    counts[drink] = counts.get(drink, 0) + 1
print(counts)
for drink, n in sorted(counts.items(), key=lambda p: p[1], reverse=True):
    print(f"{drink:<12}{'#' * n} {n}")
Output
{'latte': 4, 'drip': 3, 'mocha': 1, 'tea': 1, 'cappuccino': 1}
latte       #### 4
drip        ### 3
mocha       # 1
tea         # 1
cappuccino  # 1

The standard library offers a ready-made counting dictionary, collections.Counter, which does the same in one line and adds a most_common method. It is worth knowing, but understanding the get pattern first means you know what Counter does underneath and can adapt the idea, for example to add up sales amounts per drink instead of counting them.

Try it yourself

Bags sold per coffee

sales is a list of (code, bags) tuples, one per online order. Write a function bags_by_code(sales) that returns a dictionary mapping each stock code to the total bags ordered. With the starter data the result is {'RC-101': 5, 'RC-104': 1, 'RC-106': 3}.

Use get with a default of 0 to add each order to the running total for its code.

sales = [("RC-101", 2), ("RC-104", 1), ("RC-101", 3), ("RC-106", 3)]

def bags_by_code(sales):
    totals = {}
    return totals

print(bags_by_code(sales))

Dictionaries of records

A dictionary's values can be lists, tuples or other dictionaries. A dictionary keyed by stock code whose values are themselves dictionaries, one per coffee, is a natural way to hold the whole coffee list: each record has named fields, such as name, price and stock, instead of positions that must be remembered. This nested structure is also exactly what the JSON files of lesson 10 look like.

A dictionary of records, one inner dictionary per coffee
coffees = {
    "RC-101": {"name": "Sunrise Blend", "price": 18.50, "stock": 42},
    "RC-102": {"name": "Ethiopia Guji", "price": 22.00, "stock": 15},
    "RC-104": {"name": "Sumatra Dark", "price": 20.25, "stock": 9},
}
print(coffees["RC-104"]["name"])
coffees["RC-104"]["stock"] += 30
for code, c in coffees.items():
    print(f"{code}  {c['name']:<15}{c['stock']:>4} bags  {c['price']:>6.2f}")
Output
Sumatra Dark
RC-101  Sunrise Blend    42 bags   18.50
RC-102  Ethiopia Guji    15 bags   22.00
RC-104  Sumatra Dark     39 bags   20.25

Reading coffees['RC-104']['name'] from left to right: look up RC-104 to get that coffee's record, then look up name in the record. Inside an f-string, the inner key uses single quotes because the f-string itself uses double quotes. Compared with the list of tuples in lesson 8, a dictionary of records costs a little more typing but is much easier to read and extend, because adding a new field, such as origin or roast level, does not shift any positions.

A dictionary comprehension builds a dictionary in one expression, just as a list comprehension builds a list. {code: c['stock'] for code, c in coffees.items()} extracts the stock levels into a simple dictionary, and adding an if clause filters the entries.

Dictionary comprehensions
coffees = {
    "RC-101": {"name": "Sunrise Blend", "price": 18.50, "stock": 42},
    "RC-104": {"name": "Sumatra Dark", "price": 20.25, "stock": 9},
    "RC-105": {"name": "Decaf Swiss Water", "price": 19.00, "stock": 12},
}
values = {code: round(c["price"] * c["stock"], 2) for code, c in coffees.items()}
print(values)
low = {code: c["name"] for code, c in coffees.items() if c["stock"] < 10}
print(low)
Output
{'RC-101': 777.0, 'RC-104': 182.25, 'RC-105': 228.0}
{'RC-104': 'Sumatra Dark'}

Keys must be immutable values, such as strings, numbers or tuples of them, because a dictionary relies on a key never changing. A list cannot be a key. Values have no restriction at all.

Sets

A set is an unordered collection of unique values, written in curly braces without colons, such as {'latte', 'mocha'}. Adding a value that is already there has no effect, which makes sets the simplest way to remove duplicates: set(items) keeps one of each. Membership tests with in are very fast on sets, as on dictionary keys. The empty set must be written set(), because {} is an empty dictionary.

A set keeps one copy of each value
orders_today = ["RC-101", "RC-104", "RC-101", "RC-106", "RC-104", "RC-101"]
distinct = set(orders_today)
print(len(distinct), "different coffees ordered")
print(sorted(distinct))           # sets have no order, so sort them for display
seen = set()
seen.add("latte")
seen.add("latte")
print(seen)
Output
3 different coffees ordered
['RC-101', 'RC-104', 'RC-106']
{'latte'}

Sets support the operations of mathematical set theory. The union a | b holds everything in either set; the intersection a & b holds what is in both; the difference a - b holds what is in a but not in b; and the symmetric difference a ^ b holds what is in exactly one of them. These answer real questions in a single expression.

Set operations answer questions about two groups at once
online = {"RC-101", "RC-102", "RC-104", "RC-106"}
cafe = {"RC-101", "RC-103", "RC-104", "RC-105"}
print("Sold anywhere:", sorted(online | cafe))
print("Sold in both:", sorted(online & cafe))
print("Online only:", sorted(online - cafe))
print("In exactly one:", sorted(online ^ cafe))
all_codes = {"RC-101", "RC-102", "RC-103", "RC-104", "RC-105", "RC-106"}
print("Not sold at all:", sorted(all_codes - (online | cafe)))
Output
Sold anywhere: ['RC-101', 'RC-102', 'RC-103', 'RC-104', 'RC-105', 'RC-106']
Sold in both: ['RC-101', 'RC-104']
Online only: ['RC-102', 'RC-106']
In exactly one: ['RC-102', 'RC-103', 'RC-105', 'RC-106']
Not sold at all: []

Sets are printed in no particular order, which is why the listing sorts them for display. Because they are unordered, sets have no indexes, so s[0] is an error. When you need uniqueness and fast membership but not order, a set is the right tool; when you need order or duplicates, use a list; when you need to look things up by a key, use a dictionary.

Choosing a collection type
NeedUseExample
Items in order, duplicates allowedlistSales in the order they happened
A fixed record that should not changetuple(code, name, price)
Look up a value by a keydictStock level by stock code
Unique items, fast membership, set algebrasetCoffees sold online this week

Worked problem

Restocking from a roast log

Kai's roast log for the day is a list of (code, bags) tuples. Theo's stock is a dictionary keyed by code. Write the steps to add each batch to the stock, reject any code that is not on the coffee list, and report the new levels.

Show solution
  1. Loop over the roast log with for code, bags in log.
  2. Check the key: if code not in stock, print a message naming the unknown code and continue to the next batch.
  3. Otherwise update: stock[code] += bags.
  4. Report: loop over sorted(stock.items()) and print each code with its new level.
  5. Test with a log that includes a typo such as 'RC-10' to make sure the check works.

Answer: Known codes are restocked, the typo is reported and skipped, and the report shows the new levels.

Restocking a dictionary from a roast log, rejecting unknown codes
stock = {"RC-101": 42, "RC-102": 15, "RC-103": 27,
         "RC-104": 9, "RC-105": 12, "RC-106": 30}
log = [("RC-104", 30), ("RC-10", 30), ("RC-105", 30)]
for code, bags in log:
    if code not in stock:
        print("Unknown code in roast log:", code)
        continue
    stock[code] += bags
for code, bags in sorted(stock.items()):
    print(code, bags)
Output
Unknown code in roast log: RC-10
RC-101 42
RC-102 15
RC-103 27
RC-104 39
RC-105 42
RC-106 30

Try it yourself

Coffees sold in both channels

Write a function sold_in_both(online_orders, cafe_orders) that takes two lists of stock codes, which may contain repeats, and returns a sorted list of the codes that appear in both. With the starter data it returns ['RC-101', 'RC-104'].

online_orders = ["RC-101", "RC-104", "RC-101", "RC-106"]
cafe_orders = ["RC-103", "RC-104", "RC-101", "RC-104"]

def sold_in_both(online_orders, cafe_orders):
    return online_orders + cafe_orders

print(sold_in_both(online_orders, cafe_orders))

Common mistakes

  • MistakeLooking up a key that might be missing with d[key].

    InsteadUse d.get(key, default) or check key in d first.

  • MistakeExpecting in to search the values of a dictionary.

    Insteadin checks keys; use value in d.values() to search values.

  • MistakeWriting {} for an empty set.

    Instead{} is an empty dictionary; write set().

  • MistakeExpecting a set or dictionary to have positions, as in s[0].

    InsteadSets have no positions; dictionaries are indexed by key. Sort into a list if you need order.

  • MistakeUsing a list as a dictionary key.

    InsteadKeys must be immutable; use a tuple or a string instead.

  • MistakeChanging a dictionary's keys while looping over it.

    InsteadLoop over list(d.items()) or build a new dictionary.

Summary and key terms

  • A dictionary maps unique, immutable keys to values; d[key] looks up, assigns or adds an entry.
  • A missing key raises KeyError; get(key, default) and key in d avoid it.
  • Loop over keys, values() or items(); sort items with a key function to order a report.
  • counts[x] = counts.get(x, 0) + 1 is the standard counting pattern.
  • Dictionaries of records give named fields and match the shape of JSON data.
  • A set holds unique values; set(items) removes duplicates; | & - ^ give union, intersection, difference and symmetric difference.
  • Choose list for order, tuple for fixed records, dict for lookup by key and set for uniqueness.
dictionary (dict)
A collection of key-value pairs with fast lookup by key.
key
The unique, immutable value used to look up an entry in a dictionary.
value
The data stored under a key.
KeyError
The error raised when a dictionary lookup uses a missing key.
items()
A view of a dictionary's key-value pairs.
nested dictionary
A dictionary whose values are themselves dictionaries.
dictionary comprehension
An expression that builds a dictionary, such as {k: v for k, v in pairs}.
set
An unordered collection of unique values.
union / intersection
Set operations giving everything in either set, or only what is in both.
hashable
Usable as a dictionary key or set member because the value cannot change.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. d = {"a": 1}. What do d.get("b") and d.get("b", 0) return?

    Show answer

    None and 0.

  2. Write a loop that prints each key and value of prices = {"latte": 5.25, "tea": 3.00}.

    Show answer

    for drink, price in prices.items(): print(drink, price)

  3. What is len({"latte", "mocha", "latte"})?

    Show answer

    2, because sets keep one copy of each value.

  4. Given a = {1, 2, 3} and b = {3, 4}, what are a | b, a & b and a - b?

    Show answer

    {1, 2, 3, 4}, {3} and {1, 2}.

  5. How do you find the key with the largest value in d?

    Show answer

    max(d, key=d.get)

  6. Why can (2026, 11) be a dictionary key but [2026, 11] cannot?

    Show answer

    Tuples are immutable and hashable; lists are mutable.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz