Skip to main content

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'

Next up in your learning path