KeyError in Python: where it comes from and how to fix it

KeyError reports a key missing from a dictionary. Where it comes from, how to read it, and when to use get instead of square brackets.
5 min read
Believemy logo

Definition

A dictionary only knows the keys it was given. As long as the code stays within that set, the key points to its value. The trouble starts when the code asks for a key that was never stored, by typo, by oversight, or because the data meant to create it never arrived. What should Python do at that point?

It could have handed back None without a word, letting the program carry on with an empty value. It does the opposite: it raises KeyError and stops everything, at the exact spot of the faulty read. A missing key almost always signals missing data, and letting a None through in its place would push the problem further down, far harder to trace back to its cause.

Here is what that looks like on a two entry dictionary, when a third key that does not exist is read:

PYTHON
# The dictionary only holds two keys
customer = {"name": "Dupont", "city": "Lyon"}
customer["email"]

# KeyError: 'email'

The message holds a single word: the requested key, in single quotes. It is one of the rare Python messages that prints the offending value directly, which makes the diagnosis almost immediate. Its counterpart for lists or strings is the IndexError: same refusal, applied to a position rather than a name.


Where a missing key comes from

A missing key hardly ever shows up by chance. It almost always comes from one of three situations, and knowing which one it is points straight to the fix.

The first is a spelling gap. A dictionary key is compared character by character: "Email", "email" and "email " are three distinct keys, and the trailing space stays invisible on rereading. The type matters just as much: 1 and "1" never point at the same entry.

The second comes from the data itself. An optional field of a json response can exist on the first nine hundred records and vanish on the nine hundred and first, because a user never filled it in. The code has not changed, the data has, which is why this error shows up so often in production while the tests were passing.

The third comes from a key that is built rather than typed in directly, through concatenation or inside a loop. As soon as a key is computed, it can be computed wrong, with no error raised at that moment: the problem only surfaces when it is read, sometimes inside a different function.

Warning

Wrapping the read in a silent try that catches KeyError and just carries on makes the error message disappear along with the error itself. The program keeps running with a missing value hiding inside it, and the bug resurfaces much later, in a shape that no longer looks like a dictionary.


The writings that avoid the error

Once the cause is identified, the remaining choice is how the code should react to a key that can be missing. Five writings are available, and they do not answer the same situation.

WritingWhat it brings
data.get("key")Hands back None instead of raising
data.get("key", 0)Hands back the fallback value of your choice
"key" in dataChecks presence before reading
try and except KeyErrorCatches the case and decides what follows
data.setdefault("key", [])Creates the entry on first access

get suits a genuinely optional key, one whose absence carries a clear business meaning: a profile with no photo, an order with no promo code. try and except suit an absence that is an anomaly worth recording before deciding what to do next. One question is worth asking first: does this absence make sense, or is it hiding a problem elsewhere?

Good to know

When the fallback value is always the same empty list or empty dictionary, collections.defaultdict avoids repeating setdefault at every write: the dictionary creates the missing entry by itself the moment it is read.


Silencing the error is not fixing it

Replacing every pair of square brackets with get is tempting, and often regrettable. A required key that is missing is the symptom of a problem upstream: an incomplete import, a field renamed, a filter that is too wide. Turning it into None fixes nothing, it only pushes the fault further down, and it resurfaces in a shape far harder to trace back to its cause.

The right question to ask is therefore this one: can this key legitimately be absent? If the answer is yes, an explicit fallback value is the right answer. If the answer is no, let the exception travel up unchanged: the traceback names the file, the line and the offending key, which is almost always enough to identify whatever produced the incomplete data.


Frequently asked questions

Question

What is the difference with IndexError?

Both report an access to something absent, but not on the same kind of structure. KeyError concerns dictionaries and sets, which look up their elements by a named key. IndexError concerns lists, tuples and strings, which do the same by a numeric position. A single question settles which one to expect: names or ranks?

Question

Why does a key that is plainly on screen still raise the error?

Because the display shows neither trailing spaces nor the difference between the number 1 and the string "1", two details that still matter a great deal to Python. Comparing the requested key against the real contents of data.keys(), rather than against how the dictionary looks, settles the doubt in a single line.

Question

How can the keys a dictionary really holds be listed?

A print of data.keys() placed just before the offending line answers in a second. On a nested structure, it is better to show the parent level than the whole root: the error always concerns the last pair of brackets evaluated, never the first.

Related terms

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.