Fix KeyError in Python
Error Reference
KeyError
A dictionary lookup failed because the key isn't present. Decide whether to provide defaults or raise an explicit error.
The missing key
Python tells you exactly which key it couldn't find — the last line of the traceback shows its repr:
user = {'name': 'Ada', 'email': 'ada@example.com'}
print(user['age'])
Traceback (most recent call last):
File "profile.py", line 2, in <module>
print(user['age'])
~~~~^^^^^^^
KeyError: 'age'
Read that repr carefully. KeyError: 'age' (with quotes) means the string 'age' was looked up; KeyError: 0 (no quotes) means the integer 0 was — a different key entirely. Type mismatches like that are behind many "but the key is right there" moments.
Strategies
Use [] when the key is required — a loud crash at the lookup site beats a None that surfaces three functions later. Use .get() when the key is genuinely optional:
config = {'host': 'localhost'}
print(config['host']) # required -- crash loudly if missing
print(config.get('port')) # None
print(config.get('port', 5432)) # 5432
Catch KeyError when you want to translate absence into a clearer error for the caller:
data = {'name': 'Ada'}
try:
value = data['id']
except KeyError:
raise ValueError("Missing 'id' field")
defaultdict and setdefault
Accumulation code — counting, grouping — constantly touches keys that don't exist yet. setdefault inserts a default and returns it in one step:
groups = {}
for word in ['apple', 'avocado', 'banana']:
groups.setdefault(word[0], []).append(word)
print(groups) # {'a': ['apple', 'avocado'], 'b': ['banana']}
collections.defaultdict builds the default automatically on any missing key, so a KeyError becomes impossible:
from collections import defaultdict
counts = defaultdict(int)
for word in ['spam', 'eggs', 'spam']:
counts[word] += 1
print(counts['spam']) # 2
print(counts['ham']) # 0 -- created on access, never a KeyError
Where KeyErrors hide
JSON API responses. Optional fields simply aren't there — use .get() for anything the API doesn't guarantee:
payload = {'user': {'name': 'Ada'}} # no 'plan' field
plan = payload['user'].get('plan', 'free') # payload['user']['plan'] would raise KeyError: 'plan'
print(plan) # free
Environment variables. os.environ is a mapping, so a misspelled or unset variable raises KeyError:
import os
db = os.environ['DATABSE_URL'] # KeyError: 'DATABSE_URL' -- note the typo
import os
db = os.environ.get('DATABASE_URL', 'sqlite:///dev.db') # explicit fallback
del on an absent key. Deleting raises just like reading; pop with a default doesn't:
cache = {}
cache.pop('stale', None) # removes if present, silent if not
del cache['stale'] # KeyError: 'stale'
Frequently Asked Questions
Why do I get KeyError: 0 after loading JSON?
JSON object keys are always strings, so a dict with integer keys comes back with string keys after a round-trip. Looking up 0 then fails because only "0" exists. Convert the keys back, or look up the string form.
import json
scores = {0: 'zero'}
restored = json.loads(json.dumps(scores))
print(restored) # {'0': 'zero'}
print(restored['0']) # zero
print(restored[0]) # KeyError: 0
How do I check if a key exists?
Use the in operator for a plain membership test. Prefer try/except when the key is usually present — you pay for the check only in the rare failure case — or a single .get() call when a default value is all you need.
config = {'host': 'localhost'}
if 'port' in config:
print(config['port'])
What is the difference between KeyError and AttributeError?
KeyError comes from bracket lookup on a mapping (d['x']); AttributeError comes from dot access on an object (obj.x). Parsed JSON gives you plain dicts, so fields need brackets — dot access on a dict raises AttributeError, not KeyError.
data = {'name': 'Ada'}
print(data['name']) # Ada
print(data.name) # AttributeError: 'dict' object has no attribute 'name'