Dictionaries
A list stores values in order, and you reach each one by its numeric position. A dictionary instead stores key-value pairs, and you reach each value by its key. That makes a dictionary the right choice whenever the data is naturally labeled, such as a part’s name, subsystem, and mass, rather than numbered.
The Python documentation has a guide on Dictionaries.
Creating a Dictionary
A dictionary is written with curly braces, with each key separated from its value by a colon:
part = {
"name": "proptank",
"subsystem": "propulsion",
"mass": 1200.0,
}
print(part)
print(type(part))
print(len(part))
{'name': 'proptank', 'subsystem': 'propulsion', 'mass': 1200.0}
<class 'dict'>
3
Keys are usually strings, though any immutable value can be used.
len() reports the number of key-value pairs.
Accessing Values
A value is looked up by writing its key in square brackets, the same syntax a list uses for an index:
part = {"name": "proptank", "subsystem": "propulsion", "mass": 1200.0}
print(part["name"])
print(part["mass"])
print("mass" in part)
print("cost" in part)
print(part.get("cost")) # returns None instead of raising an error
print(part.get("cost", 0.0)) # or supply a default value
proptank
1200.0
True
False
None
0.0
Looking up a key that doesn’t exist with part["cost"] raises a KeyError.
The in operator checks whether a key is present, and .get() looks up a key without raising an error, returning None (or a default value you supply) when the key is missing.
Note that a dictionary is not indexed by position, so part[0] raises a KeyError rather than returning the first pair.
Adding, Updating, and Removing Entries
Assigning to a key either updates it, if it already exists, or adds it if it doesn’t:
part = {"name": "proptank", "mass": 1200.0}
part["mass"] = 1150.0 # update an existing key
part["is_reusable"] = True # add a new key
print(part)
del part["is_reusable"]
print(part)
removed = part.pop("mass") # remove a key and return its value
print(removed, part)
{'name': 'proptank', 'mass': 1150.0, 'is_reusable': True}
{'name': 'proptank', 'mass': 1150.0}
1150.0 {'name': 'proptank'}
del removes a key outright, while .pop() removes the key and returns its value.
Iterating Over a Dictionary
Looping over a dictionary directly gives its keys.
.values() gives the values, and .items() gives both at once as a pair:
part = {"name": "proptank", "subsystem": "propulsion", "mass": 1200.0}
for key in part:
print(key)
for value in part.values():
print(value)
for key, value in part.items():
print(f"{key}: {value}")
name
subsystem
mass
proptank
propulsion
1200.0
name: proptank
subsystem: propulsion
mass: 1200.0
for key, value in part.items() unpacks each pair into two loop variables, which is usually what you want when you need both the label and the number.
Dictionary Comprehensions
A dictionary comprehension works like a list comprehension, except it produces key: value pairs:
squares = {n: n ** 2 for n in range(6)}
print(squares)
parts_by_mass = {"booster": 25000, "second stage": 4500, "fairing": 1900}
heavy_parts = {name: mass for name, mass in parts_by_mass.items() if mass > 2000}
print(heavy_parts)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{'booster': 25000, 'second stage': 4500}
The pattern is {key_expression: value_expression for item in iterable}, and an if clause filters items the same way it does in a list comprehension.
Example: Aircraft Mass Breakdown
Question
An aircraft’s takeoff mass is made up of the empty aircraft, fuel, passengers, and cargo. Given each component’s mass, compute the total takeoff mass, what fraction of the total each component represents, and which component is heaviest.
Solution
sum over .values() totals the masses, a dictionary comprehension converts each mass to a fraction, and max with key= finds the heaviest component by its value rather than its name.
component_masses = {
"empty aircraft": 45000,
"fuel": 18000,
"passengers": 9500,
"cargo": 4200,
}
total_mass = sum(component_masses.values())
print(f"Total mass: {total_mass} lb")
fractions = {name: mass / total_mass for name, mass in component_masses.items()}
for name, fraction in fractions.items():
print(f"{name}: {fraction:.3f}")
heaviest = max(component_masses, key=component_masses.get)
print(f"Heaviest component: {heaviest}")
Total mass: 76700 lb
empty aircraft: 0.587
fuel: 0.235
passengers: 0.124
cargo: 0.055
Heaviest component: empty aircraft
A Dictionary as a Lookup Table
Because a dictionary maps one value to another, it works well as a lookup table, replacing a long chain of if-elif comparisons:
def orbit_description(category):
descriptions = {
"LEO": "Low Earth Orbit",
"MEO": "Medium Earth Orbit",
"GEO": "Geostationary Orbit",
"HEO": "High Earth Orbit",
}
return descriptions.get(category, "Unknown category")
print(orbit_description("LEO"))
print(orbit_description("XYZ"))
Low Earth Orbit
Unknown category
.get() with a default handles the case where the category isn’t in the table, which is the equivalent of the final else in an if-elif chain.
Counting with a Dictionary
Counting how many times each value appears is a common use of .get() with a default of zero:
readings = ["nominal", "nominal", "warning", "nominal", "critical", "warning"]
counts = {}
for r in readings:
counts[r] = counts.get(r, 0) + 1
print(counts)
{'nominal': 3, 'warning': 2, 'critical': 1}
On the first pass for a given status, counts.get(r, 0) returns 0 because the key isn’t there yet, so the count starts at 1.
On later passes it returns the running count, which then increases by one.
Sorting a Dictionary
sorted() on a dictionary sorts its keys.
To sort by value instead, sort .items() and tell sorted which part of each pair to use:
counts = {"nominal": 3, "warning": 2, "critical": 1}
print(sorted(counts)) # sorts the keys alphabetically
by_count = sorted(counts.items(), key=lambda pair: pair[1], reverse=True)
print(by_count)
for status, n in by_count:
print(f"{status}: {n}")
['critical', 'nominal', 'warning']
[('nominal', 3), ('warning', 2), ('critical', 1)]
nominal: 3
warning: 2
critical: 1
The key= argument takes a function that receives one item and returns the value to sort on.
Here lambda pair: pair[1] is a small unnamed function that returns the second element of each (key, value) pair, so the sort uses the counts rather than the status names.
Reading Questions
- What is the main difference between how you access a value in a list and in a dictionary?
- What happens if you look up a key that doesn’t exist with square brackets? How does
.get()behave differently? - How do you check whether a key exists in a dictionary?
- What is the difference between
del part["mass"]andpart.pop("mass")? - What does
.items()produce when used in a for loop? - Write a dictionary comprehension that maps each number from 1 to 5 to its cube.
- Why might you use a dictionary instead of a long
if-elifchain? - How would you sort a dictionary by its values rather than its keys?