Nested Data Structures

Lists of dictionaries, dictionaries of lists, and how to walk data that arrives the way real data arrives.

Overview

Reading a path

people[0]["langs"][1]

Left to right: index the list, look up the key, index that list. Each step returns something, and the next step operates on it. If you are unsure what a line does, evaluate it one piece at a time — people[0], then people[0]["langs"] — which is exactly what an editor's debugger shows you.

nested.py

nested.py Python 3
Output

                    

nested_safe.py

nested_safe.py Python 3
Output

                    

Worth knowing

Read the access left to right: people[0]["langs"][1] is index, key, index.
A chain of [] raises at the first missing level.
.get(k, {}) lets the next .get keep working.
A nested comprehension flattens: [x for row in rows for x in row].

Nested Data Structures: A Practical Guide

Real data is rarely a flat list. It is a list of records, each with fields, some of which are themselves lists. Working with it is the same handful of moves repeated at different depths.

Walking it

for person in people:
    for lang in person["langs"]:

The outer loop takes records, the inner takes the list inside each one. That is the shape of most processing you will do, and the flatten version of it is a nested comprehension:

[lang for person in people for lang in person["langs"]]

Same clause order as the loops, and worth using only when it fits on one line.

The missing-key problem

data["user"]["settings"]["theme"]

raises as soon as any level is absent, and the KeyError names only the key that failed, not the path you were walking. Two ways to be safe:

data.get("user", {}).get("settings", {}).get("theme", "default")

Each get returns {} rather than None when absent, so the next get still has a dictionary to call. That {} default is the trick that makes the chain work.

For anything deeper than two or three levels, a small helper is clearer than a long chain, and a try/except KeyError around the whole path is clearer still when a missing value really is exceptional.

Building nested shapes

Flat rows into groups is the most common transformation:

teams.setdefault(team, []).append(name)

and summing across two levels is a single generator expression:

sum(item["price"] for o in orders for item in o["items"])

Printing it

Nested structures print as one dense line. json.dumps(data, indent=2) renders them readably and is the fastest way to see the shape of something you have just parsed. It only works for JSON-compatible types, which covers most data that arrived as JSON in the first place.

Check yourself

0 of 3

Answer without scrolling back up.

  1. `people[0]['langs'][1]` reads as:

  2. Why use `.get('user', {})` rather than `.get('user')` in a chain?

  3. What does `[x for row in rows for x in row]` do?

Cheat sheet

Nested Data Structures

Real data is rarely a flat list. It is a list of records, each with fields, some of which are themselves lists. Working with it is the same handful of moves repeated at different depths.

PYTHON · vizlearn.in/python/nested_data_structures.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.