PythonMastery
reference 3 min read · lesson 11 of 45 in Errors

KeyError: 'X'

1 · The lesson

read

What this error means

A KeyError is raised when you try to access a key in a dictionary (or a dict-like object) that does not exist. Python is telling you: "I looked for that key, it's not here." It is the dictionary equivalent of IndexError for lists.

When you see it

python
>>> user = {"name": "Grace", "age": 28}
>>> user["email"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'email'

Why it happens

Square-bracket access on a dict (d[k]) demands that k is already present. If it isn't, Python refuses to invent a value and raises KeyError. This often happens after parsing JSON, reading config files, or processing user input where a field is optional or misspelt.

How to fix it

1. Use .get() when the key may be missing (returns None by default):

python
email = user.get("email")
if email is None:
    print("no email on file")
+ setup added so this can run · defines user
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

user = _AutoMock('user')

2. Use .get(key, default) to supply a fallback:

python
email = user.get("email", "unknown@example.com")
+ setup added so this can run · defines user
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

user = _AutoMock('user')

3. Check membership explicitly with in:

python
if "email" in user:
    send_to(user["email"])
+ setup added so this can run · defines user, send_to
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

user = _AutoMock('user')
def send_to(*_a, **_kw):
    print('-> send_to() called')
    return _AutoMock('send_to()')

4. Use setdefault to initialise a key if absent:

python
counts = {}
for word in words:
    counts.setdefault(word, 0)
    counts[word] += 1
+ setup added so this can run · defines words
words = ["alpha", "beta", "gamma"]

For counting specifically, collections.defaultdict(int) or collections.Counter is cleaner.

When you'd actually see this in real code

  • Parsing JSON from an API where a field is optional: data["address"]["postcode"] blows up if the user has no address.
  • Dict comprehensions filtering data: {k: src[k] for k in keys} raises KeyError the moment one of keys is missing from src. Use {k: src.get(k) for k in keys} or filter keys first.
  • Config lookups where an environment variable wasn't set: prefer os.environ.get("API_KEY") over os.environ["API_KEY"] unless you genuinely want to crash on missing config.

See Also

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code actually did, not just whether a test passed.