Chapters
Python114 chapters

CollectionsChapter 38 of 114

Nested Dictionaries

Dictionaries inside dictionaries, and how to read them without crashing.

Structure inside structure

A value can be another dictionary, or a list, to any depth. This is the shape JSON arrives in, so you will meet it constantly:

Python
people = {
    "ada": {"born": 1815, "fields": ["maths", "computing"]},
    "grace": {"born": 1906, "fields": ["computing"]},
}

print(people["ada"]["born"])
print(people["ada"]["fields"][0])

Output

1815
maths

Reading safely, one level at a time

Chained [] fails at whichever level is missing, and the KeyError only names that level:

Python
people = {"ada": {"born": 1815}}
try:
    print(people["alan"]["born"])
except KeyError as problem:
    print("KeyError:", problem)

Output

KeyError: 'alan'

Chaining get() is safe but reads badly past two levels:

Python
people = {"ada": {"born": 1815}}
print(people.get("alan", {}).get("born"))
print(people.get("ada", {}).get("born"))

Output

None
1815

The {} default is what makes the second get() legal — without it you would be calling get() on None.

A small helper is clearer when the path is long:

Python
def dig(data, *keys, default=None):
    for key in keys:
        if not isinstance(data, dict) or key not in data:
            return default
        data = data[key]
    return data

people = {"ada": {"address": {"city": "London"}}}
print(dig(people, "ada", "address", "city"))
print(dig(people, "alan", "address", "city", default="unknown"))

Output

London
unknown

Building nested structures

Assigning into a level that does not exist yet fails:

Python
groups = {}
try:
    groups["maths"]["ada"] = True
except KeyError as problem:
    print("KeyError:", problem)

Output

KeyError: 'maths'

setdefault() creates the level if it is missing and returns it either way:

Python
groups = {}
groups.setdefault("maths", []).append("ada")
groups.setdefault("maths", []).append("grace")
groups.setdefault("computing", []).append("katherine")
print(groups)

Output

{'maths': ['ada', 'grace'], 'computing': ['katherine']}

defaultdict does the same with less repetition when every value has the same shape:

Python
from collections import defaultdict

groups = defaultdict(list)
groups["maths"].append("ada")
groups["maths"].append("grace")
print(dict(groups))

Output

{'maths': ['ada', 'grace']}
Python
from collections import defaultdict

groups = defaultdict(list)
print(groups["physics"])
print(dict(groups))

Output

[]
{'physics': []}

Walking a nested structure

Python
people = {
    "ada": {"born": 1815, "fields": ["maths"]},
    "grace": {"born": 1906, "fields": ["computing", "navy"]},
}

for name, details in people.items():
    fields = ", ".join(details["fields"])
    print(f"{name}: born {details['born']}, {fields}")

Output

ada: born 1815, maths
grace: born 1906, computing, navy

Copying is shallow here too

Python
from copy import deepcopy

original = {"ada": {"fields": ["maths"]}}
shallow = original.copy()
deep = deepcopy(original)

shallow["ada"]["fields"].append("computing")
print(original["ada"]["fields"])
print(deep["ada"]["fields"])

Output

['maths', 'computing']
['maths']

The shallow copy shares the inner dictionary. Only deepcopy gives you a structure you can change without touching the original.

Test yourself

2 questions

Why does people.get("alan", {}).get("born") not crash?

Show the answer

The {} default means the second get() is called on a dictionary — Without the default the first get() returns None, and None has no get().

What does reading a missing key on a defaultdict do?

Show the answer

Creates the entry with a default value — Merely reading adds the key. Use 'in' to check without creating it.

Next chapter

Dictionary Methods

The full set of dict methods, and which ones change the dictionary.