A single dictionary describes one thing, with a labeled value for each of its properties. Engineering data usually comes as many such things: one row per flight, per test run, or per part. Putting dictionaries in a list gives you a table, where the list holds the rows and each dictionary’s keys are the columns.

Structure

Each element of the list is a dictionary with the same set of keys. A row is reached by its position in the list, and a field within that row by its key:

flights = [
    {"name": "Mercury-Redstone 3", "altitude_km": 187.0, "duration_min": 15.0},
    {"name": "Vostok 1", "altitude_km": 327.0, "duration_min": 108.0},
    {"name": "Freedom 7", "altitude_km": 187.0, "duration_min": 15.0},
]

print(len(flights))
print(flights[0])
print(flights[0]["name"])
print(flights[1]["altitude_km"])

3
{'name': 'Mercury-Redstone 3', 'altitude_km': 187.0, 'duration_min': 15.0}
Mercury-Redstone 3
327.0

flights[1]["altitude_km"] reads as “row 1, column altitude_km.”

Iterating Over the Rows

A for loop over the list gives one dictionary per pass:

flights = [
    {"name": "Mercury-Redstone 3", "altitude_km": 187.0, "duration_min": 15.0},
    {"name": "Vostok 1", "altitude_km": 327.0, "duration_min": 108.0},
    {"name": "Freedom 7", "altitude_km": 187.0, "duration_min": 15.0},
]

for flight in flights:
    print(f"{flight['name']}: {flight['altitude_km']} km")

Mercury-Redstone 3: 187.0 km
Vostok 1: 327.0 km
Freedom 7: 187.0 km

Note the quotes inside the f-string: the outer string uses double quotes, so flight['name'] uses single quotes to avoid ending the string early.

Filtering Rows

A list comprehension with an if clause selects only the rows meeting a condition, producing a shorter list of the same kind:

flights = [
    {"name": "Mercury-Redstone 3", "altitude_km": 187.0, "duration_min": 15.0},
    {"name": "Vostok 1", "altitude_km": 327.0, "duration_min": 108.0},
    {"name": "Freedom 7", "altitude_km": 187.0, "duration_min": 15.0},
]

high_flights = [f for f in flights if f["altitude_km"] > 200]
print(high_flights)

for f in high_flights:
    print(f["name"])

[{'name': 'Vostok 1', 'altitude_km': 327.0, 'duration_min': 108.0}]
Vostok 1

Extracting a Single Field

A comprehension without a filter pulls one field out of every row, turning a column of the table into a plain list:

flights = [
    {"name": "Mercury-Redstone 3", "altitude_km": 187.0, "duration_min": 15.0},
    {"name": "Vostok 1", "altitude_km": 327.0, "duration_min": 108.0},
    {"name": "Freedom 7", "altitude_km": 187.0, "duration_min": 15.0},
]

names = [f["name"] for f in flights]
altitudes = [f["altitude_km"] for f in flights]

print(names)
print(altitudes)

['Mercury-Redstone 3', 'Vostok 1', 'Freedom 7']
[187.0, 327.0, 187.0]

This is often the step before handing the values to NumPy or matplotlib, which work with plain sequences of numbers rather than dictionaries.

Sorting by a Field

sorted() accepts a key= function that says which value to sort on, the same way it does for a plain dictionary:

flights = [
    {"name": "Mercury-Redstone 3", "altitude_km": 187.0, "duration_min": 15.0},
    {"name": "Vostok 1", "altitude_km": 327.0, "duration_min": 108.0},
    {"name": "Freedom 7", "altitude_km": 187.0, "duration_min": 15.0},
]

by_duration = sorted(flights, key=lambda f: f["duration_min"], reverse=True)
for f in by_duration:
    print(f"{f['name']}: {f['duration_min']} min")

Vostok 1: 108.0 min
Mercury-Redstone 3: 15.0 min
Freedom 7: 15.0 min

lambda f: f["duration_min"] receives one row and returns that row’s duration, so the sort orders the rows by duration. reverse=True sorts from largest to smallest.

Summarizing the Rows

Built-in functions combine with a comprehension to total, average, or find an extreme across every row:

flights = [
    {"name": "Mercury-Redstone 3", "altitude_km": 187.0, "duration_min": 15.0},
    {"name": "Vostok 1", "altitude_km": 327.0, "duration_min": 108.0},
    {"name": "Freedom 7", "altitude_km": 187.0, "duration_min": 15.0},
]

total_duration = sum(f["duration_min"] for f in flights)
print(f"Total duration: {total_duration} min")

mean_altitude = sum(f["altitude_km"] for f in flights) / len(flights)
print(f"Mean altitude: {mean_altitude:.1f} km")

highest = max(flights, key=lambda f: f["altitude_km"])
print(f"Highest: {highest['name']}")

Total duration: 138.0 min
Mean altitude: 233.7 km
Highest: Vostok 1

max(flights, key=...) returns the whole row that has the largest value, rather than the value itself, which is what you want when you need to know which flight was highest.

Building a List of Dictionaries from a File

The File Input/Output page introduced csv.DictReader, which produces exactly this structure from a CSV file. Its values arrive as strings, so numeric fields need converting before they can be used in arithmetic:

import csv

with open("flights.txt", "w") as f:
    f.write("name,altitude_km,duration_min\n")
    f.write("Mercury-Redstone 3,187,15\n")
    f.write("Vostok 1,327,108\n")

flights = []
with open("flights.txt", "r") as f:
    for row in csv.DictReader(f):
        row["altitude_km"] = float(row["altitude_km"])
        row["duration_min"] = float(row["duration_min"])
        flights.append(row)

print(flights)
print(flights[0]["altitude_km"] + flights[1]["altitude_km"])

[{'name': 'Mercury-Redstone 3', 'altitude_km': 187.0, 'duration_min': 15.0}, {'name': 'Vostok 1', 'altitude_km': 327.0, 'duration_min': 108.0}]
514.0

Without the float() conversions, flights[0]["altitude_km"] + flights[1]["altitude_km"] would join two strings end to end instead of adding two numbers.

Example: Mass by Subsystem

Question

Given a list of spacecraft parts, where each part records a name, a subsystem, and a mass, find the total mass belonging to each subsystem, then report the subsystems from heaviest to lightest.

Solution

A loop over the rows builds a dictionary keyed by subsystem, using the same .get() with a default pattern as counting, except adding the mass rather than 1. Sorting the resulting dictionary’s items by value orders the subsystems by total mass.

parts = [
    {"name": "proptank", "subsystem": "propulsion", "mass": 1200.0},
    {"name": "engine", "subsystem": "propulsion", "mass": 450.0},
    {"name": "fairing", "subsystem": "structure", "mass": 900.0},
    {"name": "heat shield", "subsystem": "structure", "mass": 1600.0},
    {"name": "avionics bay", "subsystem": "avionics", "mass": 220.0},
]

mass_by_subsystem = {}
for part in parts:
    subsystem = part["subsystem"]
    mass_by_subsystem[subsystem] = mass_by_subsystem.get(subsystem, 0.0) + part["mass"]

print(mass_by_subsystem)

for subsystem, mass in sorted(mass_by_subsystem.items(), key=lambda pair: pair[1], reverse=True):
    print(f"{subsystem}: {mass} kg")

{'propulsion': 1650.0, 'structure': 2500.0, 'avionics': 220.0}
structure: 2500.0 kg
propulsion: 1650.0 kg
avionics: 220.0 kg

Grouping a list of rows into a dictionary keyed by one of their fields is a common way to summarize tabular data.

Reading Questions

  1. In a list of dictionaries, what does the list represent, and what does each dictionary represent?
  2. How would you read the mass field of the third row of a list named parts?
  3. Why does flight['name'] use single quotes when written inside an f-string delimited by double quotes?
  4. Write a list comprehension that keeps only the rows whose duration_min is greater than 60.
  5. What does max(flights, key=lambda f: f["altitude_km"]) return: a number, or a dictionary?
  6. Why do numeric fields read by csv.DictReader need to be converted before they are used in arithmetic?
  7. Describe how you would total a numeric field across every row in a list of dictionaries.