Batu Lab NotesPractical developer guides

Distinguish KeyError from a missing optional value

By Batu · English technical notes

Also published in our primary archive.

To distinguish KeyError from a missing optional value, use membership to make the presence decision before reading a mapping value. The outer configuration contains {"option": None} but no "limit". As the first contrast shows, config.get("option") and config.get("limit") both return None; that result alone cannot say whether the optional value was explicitly supplied as null or the key is absent.

This experiment adds a separate required-key boundary. read_config first classifies the optional option with membership, preserving its present-null state. It then indexes the required limit inside a tightly scoped try block. The absent limit takes the KeyError branch and is converted into the domain result missing-required: limit. Thus the code distinguishes two outcomes that both looked like None through get(): a deliberately present optional null and a missing required setting. The assertions cover each branch and stdout is fixed.

Membership does not validate whether None is acceptable; the schema decides that. Likewise, converting KeyError here is appropriate only because limit is a named required configuration field. Avoid a broad except KeyError around unrelated work, since it could hide a missing key from another mapping operation. Dictionary membership, indexing, and get() are standard Python 3 dictionary operations with no special newer API version requirement.

Python documentation: Mapping Types — dict

AI assistance disclosure: This synthetic example and explanation were prepared with AI assistance.

def classify_optional(mapping, key):
    if key in mapping:
        return "present-null" if mapping[key] is None else "present-value"
    return "missing"


def read_config(config):
    option_state = classify_optional(config, "option")
    try:
        limit = config["limit"]
    except KeyError:
        return option_state, "missing-required: limit"
    return option_state, f"limit: {limit}"


config = {"option": None}

assert config.get("option") is None
assert config.get("limit") is None
option_state, limit_state = read_config(config)
assert option_state == "present-null"
assert limit_state == "missing-required: limit"

print(f"option: {option_state}")
print(f"limit: {limit_state}")
option: present-null
limit: missing-required: limit