Skip to content
VLSI Mentor

Python Part I · Module 8 · Dictionaries

Safe Lookup: in, .get() and the Missing Key

Real data has holes in it. One log was truncated and has no runtime field; one tool version writes a key the others do not. A script that reads a field directly will stop dead on the first record that lacks it, and a summary covering two hundred tests will report nothing at all because of one. Python gives you two other ways to ask, and the point of this chapter is that they are not simply safer — they make a different claim about your data. Choosing between them is the same judgement Chapter 7.3 asked for between remove and discard, and it hinges on one question: is a missing key a normal situation, or evidence that something is already wrong?

Foundation14 min readPythonPart IFundamentalsDictionariesLookup

Part I · Module 8 · Chapter 8.3 · Safe Lookup: in, .get() and the Missing Key

1. What a Missing Key Does

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
result = {"test": "axi_smoke", "status": "PASS"}
print(result["runtime_s"])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
KeyError: 'runtime_s'

The error names the key it could not find. That is useful — it is telling you precisely which assumption failed.

And it is worth reading result["runtime_s"] as the claim it actually makes:

An empty dictionary behaves the same way, which matters because an empty one is a normal starting state:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print({}["anything"])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
KeyError: 'anything'

2. Asking Whether the Key Is There

The direct question is the membership operator you have used since Chapter 3.6:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print("status" in result)
print("runtime_s" in result)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False

It produces an ordinary boolean, like every other use of in:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
has = "status" in result
print(has, type(has))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True <class 'bool'>

And not in asks the opposite:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print("runtime_s" not in result)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True

The part that catches people

Now the trap, and it is a good one:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print("PASS" in result)
print("axi_smoke" in result)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
False
False

Both False — and both of those strings are plainly in the dictionary. They are values, not keys.

x in some_dict asks only one question: is x one of the keys? It does not look at values at all.

This follows from Chapter 8.1's model rather than being an arbitrary rule. A dictionary is organised by key — that is how it finds anything. Asking it about a value would mean searching every entry, which is not what the structure is built to do.

3. .get(): Asking Without Asserting

The second approach is a dictionary method that returns a value if the key is present and does not raise if it is not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print(result.get("status"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS

Identical to result["status"] when the key exists. The difference appears when it does not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print(result.get("runtime_s"))
print(type(result.get("runtime_s")))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None
<class 'NoneType'>

None rather than a KeyError. Chapter 2.8 introduced None as there is no value here, and that is exactly what it means in this position.

4. Supplying Your Own Default

None is rarely the most useful answer. A second argument says what to produce instead:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print(result.get("runtime_s", 0.0))
print(result.get("status", "UNKNOWN"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0.0
PASS

The first key is absent, so the default 0.0 comes back — a number, which arithmetic can use immediately. The second key is present, so the default is ignored entirely and the real value is returned.

That is the shape the canonical curriculum names for this chapter: a summary that must keep working when one log is missing a field. Rather than stopping, the missing runtime becomes 0.0 and the report continues.

One thing .get() does not do is add the key:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print(len(result))
result.get("runtime_s", 0.0)
print(len(result))
print("runtime_s" in result)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2
2
False

Two entries before, two after. .get() only reads — the dictionary is unchanged, and the default was produced for you rather than stored. Adding entries is Chapter 8.4.

5. The Limitation Worth Knowing

.get() cannot always distinguish missing from present but empty:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
d2 = {"runtime_s": None}
print(d2.get("runtime_s"))
print(d2.get("missing"))
print(d2.get("runtime_s") == d2.get("missing"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None
None
True

The first key exists and is mapped to None. The second does not exist at all. Both calls return None, and the two results compare equal — so on that evidence alone the two situations are indistinguishable.

Membership does tell them apart:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
d2 = {"runtime_s": None}
print("runtime_s" in d2)
print("missing" in d2)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False

6. The Default Fires on Absence, Not on Falsiness

This is the most common wrong model of .get(), and it is silent when you have it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
d3 = {"errors": 0}
print(d3.get("errors", 99))
print(d3.get("warnings", 99))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0
99

The first is 0, not 99. The key "errors" is present, so its value is returned — and that value happens to be zero. The default was never considered.

The second is 99, because "warnings" genuinely is not there.

7. Which One to Use

These are three different claims, not three levels of safety:

FormThe claim it makesWhen the key is absent
d["k"]this key must existKeyError — stops immediately
"k" in dI want to know whether it existsFalse
d.get("k", default)absence is acceptable, use this insteadreturns the default

Note what this chapter has deliberately not done: it has shown you how to ask whether a key is present, but not how to do one thing when it is and another when it is not. Acting differently on a boolean is the if statement, and Module 9 is where that arrives. Everything here works without it — .get() in particular is designed so that you often do not need a branch at all.

8. Asking About Values Instead

For completeness, since Section 2 raised it. If the question really is does this value occur anywhere, there is a way to ask:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result = {"test": "axi_smoke", "status": "PASS"}
print("PASS" in result.values())
print("NOPE" in result.values())
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False

result.values() provides the values, and membership against that checks values. Chapter 8.5 covers .values() properly.

Be clear about the difference in cost, though. "k" in d goes more or less straight to the answer, for the reason Chapter 7.2 gave; v in d.values() has to look through the values one after another, because they are not organised for finding. If you find yourself searching values often, the dictionary may be the wrong way round — Chapter 8.8 is about that.

9. Broken on Purpose

One more, because the message is specific:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print({"a": 1}.get("a", 1, 2))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: get expected at most 2 arguments, got 3

.get() takes the key and at most one default. A third argument is not a second fallback.

10. Predict, Run, Observe

Predict all eight before reading on. Three are the traps from this chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rec = {"test": "smoke", "errors": 0, "note": None}
print("test" in rec)
print("smoke" in rec)
print("errors" in rec)
print(rec.get("errors", 5))
print(rec.get("warnings", 5))
print(rec.get("note"))
print(rec.get("missing"))
print(len(rec))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True
False
True
0
5
None
None
3

"smoke" in rec is False — it is a value, not a key. Section 2.

rec.get("errors", 5) is 0, not 5: the key is present and its value is zero. Section 6.

Lines 6 and 7 both print None for completely different reasons — "note" exists and is mapped to None, "missing" does not exist. Section 5, and the reason in is sometimes the question you actually want.

And len(rec) is 3 throughout: nothing in this chapter changed the dictionary.

11. Exercises

Exercise 1 — three ways to ask. For rec = {"test": "smoke", "status": "PASS"}, write: the line that asserts "status" exists and fetches it, the line that reports whether "runtime_s" exists, and the line that fetches "runtime_s" with a default of 0.0.

Exercise 2 — predict. Say True, False or the exact value for each, then verify: (a) "status" in rec (b) "PASS" in rec (c) "PASS" in rec.values() (d) rec.get("status") (e) rec.get("nope") (f) rec.get("nope", "?")

Exercise 3 — the keys-only rule. Explain in three sentences why "PASS" in rec is False, referring to what a dictionary is organised by, and give the expression that asks the question the author probably meant.

Exercise 4 — the falsy default. Predict both, verify, and explain the difference in two sentences:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
d = {"errors": 0, "warnings": 2}
print(d.get("errors", 10))
print(d.get("fatals", 10))

Then say what would go wrong in a report that used the first form to avoid zeros.

Exercise 5 — missing or empty. Given d = {"note": None}, predict d.get("note"), d.get("absent") and "note" in d. Explain in two sentences which expression distinguishes the two situations and why .get() alone cannot.

Exercise 6 — choose the form. For each, say which of the three forms you would use and justify in one sentence: (a) reading the status of a record your own parser just built, where every record must have one (b) reading an optional comment field that only some tools emit (c) reporting how many records contain a runtime field (d) reading a count that should default to zero when the field is absent

Exercise 7 — read the error. Run {"a": 1}["b"] and write down the exact message. Say in two sentences what assumption failed and why stopping is more useful here than returning None would be.

Exercise 8 — the cost difference. Explain in two sentences why "k" in d and v in d.values() are not equally cheap, referring to how a dictionary finds things.

12. Summary

  • d["k"] asserts the key exists. When it does not, KeyError: 'k' names the failed assumption and stops.
  • "k" in d asks whether the key is present and produces a bool.
  • Membership checks keys only. "PASS" in result is False even when "PASS" is a value — silently, because the boolean is valid.
  • d.get("k") returns the value, or None if the key is absent — no exception.
  • d.get("k", default) returns your default instead of None.
  • .get() never adds the key and never changes the dictionary.
  • The default fires on absence, not on falsiness. {"errors": 0}.get("errors", 99) is 0.
  • .get() cannot distinguish a missing key from a key mapped to None — both return None. Use in when that difference matters.
  • Choose by what absence means: direct lookup when it signals a bug, .get() when it is normal variation. Neither is generally safer, and .get() everywhere hides real failures.
  • v in d.values() asks about values, and has to search rather than look up.

Reading a dictionary is settled. The next chapter changes one.

Where this fits

Part of the Python curriculum.