14 Dictionaries
14.1 The Wall
AI used a dictionary when a list would have worked, and you did not know enough to question it. In another case, your code crashed with KeyError: 'username' because the dictionary did not have the key AI assumed existed. You had no idea how to check whether a key exists before accessing it.
Dictionaries are how AI organises structured data, user profiles, configuration, API responses, keyword mappings. If you cannot read dictionary operations, you cannot work with most real-world AI-generated code.
This chapter fixes that.
You may recognise dictionaries from the Contact Book project in Think Python, Direct AI, where you used them to store names and phone numbers. Here we explore dictionaries more thoroughly, how they work, the full range of methods, and the patterns that make them essential.
14.2 Thinking Session
14.2.1 Getting Oriented
Explain Python dictionaries. How are they different from lists? When should I use a dictionary instead of a list? And what actually happens when I write user = {"name": "Alice", "age": 30}, how does Python find values by key?
Your AI should explain: dictionaries map keys to values, lists map indexes to values. Use a dictionary when you need to look things up by name, not by position. Keys must be immutable (strings, numbers, tuples) and unique. If your AI does not mention that dictionaries are unordered in older Python but ordered by insertion in Python 3.7+, that is a useful detail.
14.2.2 Go Deeper
What are the most important dictionary methods? I see AI using get(), keys(), values(), items(), update(), and pop(). Explain each one. Specifically, what is the difference between user["name"] and user.get("name"), and why does AI sometimes use one versus the other?
The critical difference: user["name"] raises KeyError if the key does not exist. user.get("name") returns None (or a default you specify). AI should use .get() for keys that might not exist. If your AI does not emphasise this, it is the single most important dictionary concept.
How do I loop through a dictionary in Python? What do I get when I use a for loop directly on a dictionary? And what is the items() method for?
14.2.3 Challenge It
What happens with each of these?
data = {"a": 1, "b": 2}
data["c"]
data.get("c")
data.get("c", 0)
"a" in data
1 in data
data["a"] = 10
data.update({"b": 20, "d": 4})data["c"] raises KeyError. data.get("c") returns None. data.get("c", 0) returns 0. "a" in data is True (checks keys). 1 in data is False (1 is a value, not a key). data["a"] = 10 updates the value. data.update(...) merges two dictionaries.
14.2.4 What You Should Have Learned
- Dictionaries map keys to values:
{"key": value} - Use
dict[key]when you know the key exists,.get(key)when you do not inchecks keys, not valuesitems()gives key-value pairs for looping- Keys must be immutable; values can be anything
- Dictionaries are mutable. You can add, change, and remove entries
14.3 The Gap
Dictionaries are how your chatbot will map keywords to responses, instead of long if/elif chains, you look up the response by category. This is the pattern AI uses in most structured programs: configuration as dictionaries, response templates as dictionaries, user data as dictionaries.
In the Building Session, you will replace the if/elif response logic with a dictionary-based approach.
14.4 Building Session
14.4.1 The Spec
Restructure your chatbot’s responses using dictionaries:
- Map response categories to lists of possible responses
- Map keywords to categories
- Use
.get()with a default for unknown categories - Add a “topics” command that shows all known categories
14.4.2 Prompt It
Update my chatbot to v1.4. Replace the if/elif response chain with dictionaries:
- Create a RESPONSES dict mapping categories (“greeting”, “question”, “farewell”, “default”) to lists of response strings
- Create a KEYWORDS dict mapping trigger words to categories (e.g., “hello” → “greeting”, “bye” → “farewell”)
- Write a classify_input(text) function that checks each word against KEYWORDS and returns the category (default to “default”)
- Update get_response() to use the dictionaries instead of if/elif
- Add a “topics” command that shows all response categories and their keyword triggers
Keep existing features.
14.4.3 Read the Code
Your AI will produce something like this:
RESPONSES = {
"greeting": [
"Hello! Great to hear from you.",
"Hi there! What's on your mind?",
],
"question": [
"Good question! Let me think...",
"That's interesting. I'm learning too.",
],
"farewell": [
"Goodbye! It was nice chatting.",
"See you later!",
],
"default": [
"Tell me more about that.",
"Interesting! Go on...",
],
}
KEYWORDS = {
"hello": "greeting",
"hi": "greeting",
"hey": "greeting",
"bye": "farewell",
"goodbye": "farewell",
"quit": "farewell",
}
def classify_input(text):
"""Map input to a response category."""
words = text.lower().split()
for word in words:
if word in KEYWORDS:
return KEYWORDS[word]
if "?" in text:
return "question"
return "default"
def get_response(user_input):
"""Get a response using dictionary lookup."""
category = classify_input(user_input)
responses = RESPONSES.get(category, RESPONSES["default"])
return random.choice(responses), category == "farewell"The if/elif chain is gone, replaced by dictionary lookups. Adding a new response category means adding entries to RESPONSES and KEYWORDS, not modifying logic. RESPONSES.get(category, RESPONSES["default"]) uses .get() with a fallback. This is how AI structures most configurable systems.
14.4.4 Stretch It
Add a “learn” command that lets the user teach the bot a new keyword-category mapping. Store it in the KEYWORDS dictionary. Test it by teaching a new keyword and then using it.
14.5 Your Chatbot So Far
- Ch 1-12: Full features with functions, loops, text processing
- Ch 13: String pattern matching
- Ch 14: Dictionary-based responses, keyword classification, configurable categories
14.6 Quick Reference
# Create
user = {"name": "Alice", "age": 30}
empty = {}
# Access
user["name"] # "Alice" (KeyError if missing)
user.get("name") # "Alice" (None if missing)
user.get("role", "guest") # "guest" (default)
# Modify
user["email"] = "a@b.com" # add/update
user.update({"age": 31}) # merge
del user["age"] # delete
user.pop("age", None) # delete (no error if missing)
# Check
"name" in user # True (checks keys)
# Loop
for key in user: # keys only
for key, value in user.items(): # both
for value in user.values(): # values only
# Comprehension
squares = {x: x**2 for x in range(5)}