Lists of Dictionaries
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
- In a list of dictionaries, what does the list represent, and what does each dictionary represent?
- How would you read the
massfield of the third row of a list namedparts? - Why does
flight['name']use single quotes when written inside an f-string delimited by double quotes? - Write a list comprehension that keeps only the rows whose
duration_minis greater than 60. - What does
max(flights, key=lambda f: f["altitude_km"])return: a number, or a dictionary? - Why do numeric fields read by
csv.DictReaderneed to be converted before they are used in arithmetic? - Describe how you would total a numeric field across every row in a list of dictionaries.