Python has this philosophy called EAFP - easier to ask forgiveness than permission. The idea is you just try stuff and catch exceptions if things go wrong. This works great until you're dealing with user input or messy data, where missing keys happen constantly.
What causes KeyError
Here's the classic failure mode:
person = {'name': 'seth', 'age': 42, 'bald': True}
print(person['name']) # ok
print(person['city']) # crashes
Output:
KeyError: 'city'
If you're building a web service that processes JSON from an API, or parsing CSV files, or handling form data - you'll hit this constantly. One missing field and your entire request handler crashes. Congrats, you just turned "slightly messy data" into "500 Internal Server Error."
Use dict.get instead
In most application code, I default to dict.get().
person = {'name': 'seth', 'age': 42, 'bald': True}
city = person.get('city', 'Unknown City')
print(city)
The second argument to get() is what gets returned if the key doesn't exist. In this case, we return "Unknown City" instead of crashing.
Yes, this goes against Python's EAFP philosophy. But in practice, it prevents crashes.
Official docs: dict.get
When you should use try/except
Sometimes a missing key really is a bug. Like required fields in validated data, or internal data structures where "missing" means "someone broke something."
try:
user_id = payload["user_id"]
except KeyError as e:
raise ValueError("payload missing required key: user_id") from e
This keeps the EAFP approach, but gives you a clean error message that explains what went wrong.
How I think about it
Here's how I think about it:
If the data might not have the key, use get() with a default.
If the key absolutely must be there and it's a bug if it's not, use direct indexing (d[key]) and let it crash. Crashing is a valid debugging strategy when the situation should be impossible.
If you need a better error message (or want to turn a KeyError into something your API consumers can understand), wrap it in try/except.
-Sethers