← Debugging Guides

阅读中文版本

Python — KeyError on a Dictionary Lookup

Trace a Python KeyError to the missing key, distinguish an absent key from a None value, and choose between defaults, validation, and explicit errors.

beginnerdebuggingpythondictionary

Symptoms

  • Traceback ends with KeyError: 'some-key'
  • The failure appears only for one input, tenant, or configuration file
  • An API returns 500 while the same code works for the default dataset

Possible causes

  • The key is absent, misspelled, or has different case than expected
  • The input JSON has a different shape than the code assumes
  • The value exists but is None, and the code indexes it as if it were a dict
  • The dictionary is mutated between the check and the lookup
  • A default was expected from os.environ or a config loader but was not set

Diagnosis steps

Read the last frame of the traceback

python -m trace --trace script.py 2>&1 | tail -n 30

Interpretation

The final frame shows the expression that raised. Identify which dictionary and which key expression produced the error.

Print the available keys at the failure point

print(sorted(data.keys()))

Interpretation

Compare the actual keys with the expected key. A missing tenant id or a renamed field is usually visible immediately.

Distinguish a missing key from a None value

print(repr(data.get('key', '<missing>')))

Interpretation

If the result is None, the key exists but the value is null and a later operation fails differently. Guard the value, not only the key.

Root cause

A dictionary lookup used a key that was not present. The key may be genuinely missing, renamed upstream, or accessed on a value that is None or a different type.

Fix

Use an explicit membership check or dict.get when absence is expected, validate required keys at the boundary, and raise a domain error with context when a required key is missing. Do not use a blanket default that hides malformed input.

Risk note

dict.get(key, {}) can turn a missing required field into a silent empty object and defer the failure to a harder-to-debug place. Choose defaults only when absence is a valid state.

Verify the fix

Add a test for the missing-key input and one for a present key with a None value. Confirm the error message includes the field and the source, and that the API returns a 4xx for invalid input rather than 500.

The traceback names the expression, not the cause

A KeyError traceback points at data['tenant_id'], but the cause may be an upstream service that renamed the field, a YAML file with a different indentation level, or a branch that built the dictionary without that key. Print the available keys and the type of the object before the lookup. The missing key is a symptom; the reason it is missing is the bug.

payload = {"tenant": "acme", "user": None}
tenant = payload["tenant_id"]  # KeyError: 'tenant_id'

A key that exists with a None value behaves differently. payload["user"]["name"] raises TypeError, not KeyError, because None is not subscriptable. Handle the two cases separately.

Choose the right absence policy

If absence is invalid, validate at the boundary and raise a clear error:

REQUIRED = ("tenant_id", "user_id")

missing = [key for key in REQUIRED if key not in payload]
if missing:
    raise ValueError(f"missing required fields: {', '.join(missing)}")

If absence is valid, use dict.get(key, default) or collections.defaultdict and make the default explicit in the type and in tests. Do not spread a generic {} default through the code; it hides malformed payloads and produces errors far from the source.

from collections import defaultdict

counts = defaultdict(int)
counts["errors"] += 1

defaultdict is appropriate when every missing key has the same meaningful default. It is a poor fit for required business fields, where a missing value should fail loudly.

Validate the shape, not just one key

If the dictionary comes from JSON, a schema validator such as Pydantic, or a small hand-written check, makes the contract explicit. Validate once at the boundary and pass a typed object into the rest of the code. That converts a scattered family of KeyErrors into one structured validation error with a field path. For configuration, fail at startup when a required environment variable is missing rather than at the first request that uses it.

Fix and verify

Fix the lookup by validating required keys at the boundary and choosing an explicit policy for optional keys. Verify the fix with a test that removes the key from a payload and asserts a clear validation error, plus a test where the key exists with a None value. Run the original failing input again and confirm it produces the domain error instead of a traceback.