Python Dictionaries: Key-Value Storage Explained
If lists are Python's ordered collection, dictionaries are its labeled one. A dictionary maps keys to values, letting you look up any value instantly by name instead of by position. This makes dicts the ideal structure for structured records β a crop's stats, a player's profile, a configuration object. In this guide you'll learn to create, access, update, iterate, and compose dictionaries.
What Is a Dictionary?
Dictionaries store pairs of keys and values. Each key must be unique and immutable (strings and integers are the most common choices). Values can be anything β numbers, strings, lists, or even other dicts.
Creating a Dictionary
Use curly braces with key: value pairs, or call dict() with keyword arguments.
# Dict literal
wheat = {
"name": "wheat",
"cost": 10,
"value": 25,
"days": 5,
"mature": False,
}
# dict() constructor
carrot = dict(name="carrot", cost=5, value=15, days=3, mature=False)
# Empty dict
player = {}
print(type(player)) # <class 'dict'>
print(len(wheat)) # 5 β number of key-value pairs
Accessing Values
Use square-bracket notation to get a value by key. If the key doesn't exist, Python raises a KeyError. Use .get() to safely return a default instead.
crop = {"name": "tomato", "cost": 15, "value": 40, "days": 7}
# Direct access β raises KeyError if key missing
print(crop["name"]) # tomato
print(crop["value"]) # 40
# .get() β returns None by default, or your chosen fallback
print(crop.get("water")) # None
print(crop.get("water", 0.5)) # 0.5 (default)
print(crop.get("name", "unknown")) # tomato
Adding and Updating Entries
Assign to a key to add it if new, or update it if it already exists. Use .update() to merge another dict or keyword arguments in one call.
player = {"name": "Alex", "gold": 100, "level": 1}
# Add a new key
player["plots"] = 4
# Update an existing key
player["gold"] += 50
print(player["gold"]) # 150
# Merge with .update()
player.update({"level": 2, "xp": 500})
print(player)
# {'name': 'Alex', 'gold': 150, 'level': 2, 'plots': 4, 'xp': 500}
Removing Entries
Three ways to remove items: .pop(key) removes and returns a value, del dict[key] removes in place, and .clear() empties the entire dict.
inventory = {"wheat": 3, "carrot": 5, "tomato": 2, "pumpkin": 0}
# .pop() β remove and get the value
count = inventory.pop("tomato")
print(count) # 2
print(inventory) # {'wheat': 3, 'carrot': 5, 'pumpkin': 0}
# .pop() with a default (no KeyError if missing)
pumpkin = inventory.pop("pumpkin", 0)
print(pumpkin) # 0
# del β in-place removal
del inventory["carrot"]
print(inventory) # {'wheat': 3}
# .clear() β empty the dict
inventory.clear()
print(inventory) # {}
Checking Keys
Use the in operator to test whether a key exists. This is O(1) β extremely fast even for large dicts.
crop_db = {"wheat": 10, "carrot": 5, "tomato": 15}
print("wheat" in crop_db) # True
print("pumpkin" in crop_db) # False
print("pumpkin" not in crop_db) # True
# Safe access pattern
crop = "tomato"
if crop in crop_db:
print(f"{crop} costs {crop_db[crop]} gold")
Iterating Over a Dictionary
Three iteration methods cover every case: .keys(), .values(), and .items(). Use .items() when you need both.
crops = {
"wheat": {"cost": 10, "value": 25},
"carrot": {"cost": 5, "value": 15},
"tomato": {"cost": 15, "value": 40},
}
# Just keys
for name in crops.keys():
print(name) # wheat, carrot, tomato
# Just values
for stats in crops.values():
print(stats["value"])
# Keys AND values β the most common pattern
for name, stats in crops.items():
profit = stats["value"] - stats["cost"]
roi = (profit / stats["cost"]) * 100
print(f"{name}: profit {profit} gold, ROI {roi:.0f}%")
Nested Dictionaries
Dictionaries can hold other dictionaries as values, making them ideal for hierarchical data like a full farm state.
farm = {
"player": {"name": "Alex", "gold": 200, "level": 3},
"season": {"name": "Spring", "day": 7, "length": 28},
"plots": {
(0, 0): {"crop": "wheat", "days": 4, "mature": False},
(1, 0): {"crop": "carrot", "days": 3, "mature": True},
}
}
# Access nested values
print(farm["player"]["gold"]) # 200
print(farm["plots"][(1, 0)]["mature"]) # True
# Update a nested value
farm["player"]["gold"] -= 50
farm["season"]["day"] += 1
Dict Comprehensions
Just like list comprehensions, you can build a dictionary in a single expression using {key: value for item in iterable}.
crop_list = [
{"name": "wheat", "cost": 10, "value": 25},
{"name": "carrot", "cost": 5, "value": 15},
{"name": "tomato", "cost": 15, "value": 40},
]
# Build a dict: name -> profit
profit_map = {
c["name"]: c["value"] - c["cost"]
for c in crop_list
}
print(profit_map)
# {'wheat': 15, 'carrot': 10, 'tomato': 25}
# Filter to only profitable crops (profit > 12)
top_crops = {k: v for k, v in profit_map.items() if v > 12}
print(top_crops) # {'wheat': 15, 'tomato': 25}
Practical: Crop Stats as a Dict
Here's how GrowBit might store and query crop data using nested dicts β a common real-world pattern in game configs and APIs alike.
CROP_DB = {
"wheat": {"name": "wheat", "cost": 10, "value": 25, "days": 5},
"carrot": {"name": "carrot", "cost": 5, "value": 15, "days": 3},
"tomato": {"name": "tomato", "cost": 15, "value": 40, "days": 7},
"pumpkin": {"name": "pumpkin", "cost": 20, "value": 60, "days": 10},
}
def best_crop_by_roi(db):
"""Return the crop name with the highest return on investment."""
return max(
db,
key=lambda name: (db[name]["value"] - db[name]["cost"]) / db[name]["cost"]
)
print(best_crop_by_roi(CROP_DB)) # pumpkin (ROI: 200%)