Accessing and Updating Dictionary Data

Once a dictionary exists, accessing its data falls into three small categories: read one value, read all values, and update values. Each one has a direct form (fast but raises on missing keys) and a forgiving form (slightly slower but safe), and mature Python code mixes both depending on whether a missing key is a bug or a normal case.

Reading a single value with d[key] is fast and raises KeyError when the key is absent. Use it when the key must be present; the exception will fail loudly and early. For optional reads, d.get(key, default) returns the default instead of raising. For reads that should insert the default on first miss, d.setdefault(key, default) does both in one call.

Iterating a dict yields its keys by default. for k in d:, for v in d.values():, and for k, v in d.items(): are the three forms. The views returned by .keys(), .values() and .items() are live: they reflect later changes to the dict, which is convenient and, once in a while, a source of subtle bugs.

Updating is equally straightforward. Assigning to a key inserts or replaces: d["age"] = 31. Calling d.update(other) merges in a mapping or an iterable of pairs. Deleting is done with del d[k] (raises on missing) or d.pop(k, None) (safe). The in operator is the standard membership test and runs in O(1).

Reading safely and explicitly

A subtle difference between get and setdefault: get never changes the dictionary, while setdefault inserts the default on the first miss. Use get when you just want to read; use setdefault when you want to read-or-create a bucket, typically when grouping or counting without a defaultdict.

For nested dictionaries (config["db"]["host"]) consider using dict.get twice or wrapping the lookup in a small helper. Deep access becomes safer and more readable with collections.ChainMap or dedicated libraries when the structure is large.

Updating without iteration errors

Modifying a dict during iteration raises RuntimeError: dictionary changed size during iteration. Collect the changes first and apply them afterwards: build a new dict with a comprehension, or iterate over list(d) if you only need a snapshot of the keys.

d.pop(key, sentinel) is the safest way to remove-and-return. It lets you distinguish “present” from “absent” without catching exceptions, which leads to tidier code than try/except KeyError in most situations.

The toolkit for reading and updating dictionary data.

ToolPurpose
d.get(k)
method
Returns d[k] or None; never raises.
d.setdefault(k, v)
method
Returns existing value or inserts v.
d.pop(k, default)
method
Removes and returns value; default if missing.
d.update(other)
method
Merges another mapping in place.
d.items()
method
View of (key, value) pairs.
d.keys()
method
View of keys (supports set operations).
k in d
operator
True if key k is in the dict.
del d[k]
statement
Removes key k, raising KeyError if absent.

Accessing and Updating Dictionary Data code example

The script below walks through read, update, delete and safe-iteration patterns on a small inventory dict.

# Lesson: Accessing and Updating Dictionary Data
inventory = {"apples": 12, "bananas": 7, "cherries": 25}

# Reading
print("apples  :", inventory["apples"])
print("grapes? :", inventory.get("grapes", 0))
print("in dict?:", "bananas" in inventory)

# Read-or-create
inventory.setdefault("dates", 0)
inventory["dates"] += 5
print("after setdefault:", inventory)

# Update and merge
inventory.update({"apples": 15, "elderberries": 3})
inventory["bananas"] = inventory.get("bananas", 0) - 2
print("after update:    ", inventory)

# Safe iteration: build new dict from old
nonzero = {k: v for k, v in inventory.items() if v > 0}
print("nonzero only:    ", nonzero)

# Safe deletion
removed = inventory.pop("elderberries", None)
missing = inventory.pop("figs", None)
print("removed / missing:", removed, missing)

# Common-keys via view
other = {"apples": 1, "bananas": 1, "lemons": 1}
print("common keys:     ", inventory.keys() & other.keys())

Here is what each block demonstrates:

1) `get(key, default)` never mutates; `setdefault` inserts when the key is missing.
2) `update(other)` merges a mapping in place; later keys win.
3) Building a new dict with a comprehension is the safe way to 'filter' during iteration.
4) `keys() & keys()` leverages the set-like views exposed by dict views.

Practice safe reads and grouping with setdefault.

# Example A: counting without Counter
text = "the quick brown fox jumps over the lazy dog"
counts: dict[str, int] = {}
for ch in text.replace(" ", ""):
    counts[ch] = counts.get(ch, 0) + 1
print(sorted(counts.items())[:5])

# Example B: safe deletion helper
def discard(d: dict, key):
    return d.pop(key, None)
print(discard({"a": 1}, "a"))     # 1
print(discard({"a": 1}, "missing")) # None

Assert the contract of each lookup/update.

d = {"a": 1}
assert d.get("a") == 1 and d.get("b") is None
assert d.setdefault("b", 0) == 0 and d["b"] == 0
d.update({"a": 2}); assert d["a"] == 2
assert d.pop("missing", 42) == 42

Running prints:

apples  : 12
grapes? : 0
in dict?: True
after setdefault: {'apples': 12, 'bananas': 7, 'cherries': 25, 'dates': 5}
after update:     {'apples': 15, 'bananas': 5, 'cherries': 25, 'dates': 5, 'elderberries': 3}
nonzero only:     {'apples': 15, 'bananas': 5, 'cherries': 25, 'dates': 5, 'elderberries': 3}
removed / missing: 3 None
common keys:      {'apples', 'bananas'}