NOETRION

Choose a topic

DAY 10 OF 30 · FUNCTIONS AND COLLECTIONS

Dictionaries and records

Store named fields, update values, and distinguish a missing key from a known zero.

90–120 minutes suggested study timePython 3.12Not yet completed

Today's outcomes

  • Choose dictionary keys and access values safely.
  • Use keys, values, and items.
  • Model a record with explicit field meanings.

Before you start: Complete Day 09 and its practical challenge.

Understand the concept

A dictionary connects unique keys to values

A dictionary is a mapping. It connects a key such as "minutes" to a value such as 45. Keys must be hashable and unique within the mapping. Values may repeat and may be mutable. Strings are common record-field keys, while integers can be useful stable identifiers.

Choose a dictionary when a named relationship matters more than a position. session['minutes'] communicates a field's meaning more clearly than session[2] in a wide positional record. The dictionary itself does not enforce which fields exist, their types, or their units. Those requirements still belong to your contract.

Python dictionaries preserve insertion order, but they do not sort keys automatically. Updating an existing key changes its value without creating a second copy of the key. A mapping therefore cannot directly preserve two independent records under one identical key; Day 15 will examine that collision problem.

Missing keys and missing values are different

record['minutes'] raises KeyError when the key is absent. record.get('minutes') instead returns None by default for an absent key. That convenience can collapse two distinct states: an absent field and an explicitly recorded None. Use a membership check or a dedicated sentinel if those states need different handling.

Zero minutes is a valid known quantity in today's fixture. The condition if not minutes would combine zero with None, empty text, and other false-valued inputs. Use the test that matches the question. minutes is None asks about the chosen missing marker; 'minutes' in record asks whether the field exists.

Views let you iterate deliberately

keys gives a view of keys, values gives a view of values, and items gives key-value pairs. Iterating the dictionary itself also yields keys. Views reflect changes to the mapping rather than being independent historical snapshots. Build a list when you need an explicit snapshot.

Avoid adding or removing dictionary entries while iterating its live view. Even when only changing values is possible, a separate output mapping can make the transformation easier to inspect. Name the operation: are you updating one record, transforming every value, or grouping several records?

Worked example

session = {"id": "S1", "topic": "functions", "minutes": 45, "score": None}
print("Topic:", session["topic"])
print("Minutes:", session.get("minutes"))
print("Score missing:", session["score"] is None)
session["minutes"] = 60
session["completed"] = True

for field, value in session.items():
    print(field, value)

assert session["minutes"] == 60
assert "completed" in session
assert "notes" not in session
assert session.get("notes", "not supplied") == "not supplied"
Operation Question answered Result
session['topic'] Required field's value functions
session['score'] is None Is this field explicitly missing? True
'notes' in session Does the field exist? False
get with explicit default Optional field or fallback not supplied

The key order for printed fields follows insertion order in this script. That is useful for readability, but your calculation should not depend on a dictionary happening to print fields in a particular position.

Guided practice

  1. Predict: create one record with minutes: 0, one with minutes: None, and one without the minutes key. Which tests distinguish all three?
  2. Modify: add a notes field and remove it with pop. Capture the returned value. Explain the difference between removing a field and assigning None to that field.
  3. Reconstruct: represent two named sessions as a list of dictionaries. Loop through the list and print their IDs and known minute values. Do not invent zero minutes for a missing field.

A shallow dictionary copy protects the outer mapping from a direct field update, but nested lists may remain shared, just as in Day 8. Inspect both representation layers when a copied record changes unexpectedly.

Daily challenge

Implement summarize_records(records). Each input item is a dictionary. Distinguish absent minutes fields from explicitly missing None values. Nonmissing minutes are assumed to be nonnegative integers. Return a dictionary containing recorded_count, missing_count, absent_count, and total_minutes. Leave the input records unchanged.

Record Category Contribution
{'minutes': 30} recorded 30 minutes
{'minutes': 0} recorded 0 minutes
{'minutes': None} missing No numeric contribution
{'topic': 'lists'} absent Missing field rather than missing observation

For those four records, expect recorded 2, missing 1, absent 1, and total 30. For an empty list, expect all counts and total to be zero. The counts must add up to the input record count.

Reference solution

Compare after attempting the challenge
def summarize_records(records):
    report = {"recorded_count": 0, "missing_count": 0,
              "absent_count": 0, "total_minutes": 0}
    for record in records:
        if "minutes" not in record:
            report["absent_count"] += 1
        elif record["minutes"] is None:
            report["missing_count"] += 1
        else:
            report["recorded_count"] += 1
            report["total_minutes"] += record["minutes"]
    return report

records = [{"minutes": 30}, {"minutes": 0}, {"minutes": None}, {"topic": "lists"}]
assert summarize_records(records) == {
    "recorded_count": 2, "missing_count": 1,
    "absent_count": 1, "total_minutes": 30}
assert summarize_records([])["recorded_count"] == 0
assert records[1]["minutes"] == 0
assert "minutes" not in records[3]
print(summarize_records(records))

The order of the first two branches preserves the distinction between an absent key and an explicit None. No exception handler is needed because membership is checked before required access.

Debug and explain

If KeyError appears, inspect which field the contract requires and which record lacks it. If a total excludes zeros, inspect truth-value checks. If a record disappears while building a mapping, inspect repeated keys rather than assuming the loop skipped an item.

Explain why get alone cannot distinguish the absent and explicit-None cases with its default. Then explain why a dictionary's unique keys do not imply that every input record has a unique identity. Identity must come from a suitable field and a stated uniqueness rule.

Review and next step

Save the four-record fixture and its accounting result. At the next review, rebuild the missing-field branches before the total. Day 11 compares collections through sets while preserving the distinction between membership and repeated observations.

Sources and verification

Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.

DAILY CHECKPOINT

Check your understanding

Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.

01. What does dictionary indexing by an absent key raise?

02. Why can get(key, 0) hide a data problem?

03. What happens when a dictionary key is assigned again?

04. What does items return for iteration?

05. Can a list be a normal dictionary key?

Review your practical work

Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.

Pass the knowledge check and finish the practical self-review to save completion.