backend / python core / tricky questions / 40_dict_missing_method.md

__missing__ — and why .get() doesn't trigger it

2 min read source

__missing__ — and why .get() doesn’t trigger it

The gotcha

A dict subclass can define __missing__(self, key) to handle missing keys for d[key]. But .get() doesn’t call it — .get() returns its default (None) silently. Mixing the two leads to inconsistent behavior depending on access syntax.

Minimal repro

class SafeDict(dict):
    def __missing__(self, key):
        return f"<{key}>"

d = SafeDict(a=1)

print(d['a'])           # 1                     normal hit
print(d['b'])           # "<b>"                 __missing__ fires
print(d.get('b'))       # None                  __missing__ NOT called
print(d.get('b', "x"))  # "x"

Why it happens

__missing__ is invoked from __getitem__ only:

d[k]   → __getitem__(k)
        → if k in self: return self[k]
        → else: return self.__missing__(k)

d.get(k) is a separate method that doesn’t call __getitem__ at all — it does its own lookup and returns the explicit default on miss. By design, .get() says “I want a default, never raise.” __missing__ is “I want to handle the miss case for [] access.”

in (__contains__), keys(), items(), pop() — none call __missing__.

How defaultdict uses it

collections.defaultdict is the canonical __missing__ user:

from collections import defaultdict

class defaultdict(dict):
    def __init__(self, default_factory=None, *args, **kw):
        super().__init__(*args, **kw)
        self.default_factory = default_factory

    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        value = self.default_factory()
        self[key] = value           # ← critical: stores so future lookups hit
        return value

The key bit is self[key] = value__missing__ not only returns the default, it inserts it. So d['a'].append(1); d['a'].append(2) accumulates correctly:

d = defaultdict(list)
d['x'].append(1)        # __missing__ creates [] and stores it; returns []; .append(1) mutates the stored list
d['x'].append(2)        # 'x' now exists; __missing__ doesn't fire; .append(2) mutates same list
print(d['x'])           # [1, 2]

When you’d write your own __missing__

Self-populating cache:

class HashLookup(dict):
    def __init__(self, source):
        super().__init__()
        self.source = source
    def __missing__(self, key):
        result = self.source.fetch(key)
        self[key] = result
        return result

String formatting safe-fallback:

class FormatDict(dict):
    def __missing__(self, key):
        return '{' + key + '}'

template = "Hello {name}, your balance is {balance}"
template.format_map(FormatDict(name="Alice"))
# 'Hello Alice, your balance is {balance}'

format_map uses [] access on the dict; missing keys preserve the placeholder rather than raising.

Metric collection:

class CounterDict(dict):
    def __missing__(self, key):
        self[key] = 0
        return 0

Better: use collections.Counter — same idea, more features.

Subtle caveat: __missing__ doesn’t cover all access

class MyDict(dict):
    def __missing__(self, key):
        return "default"

d = MyDict()
print(d['x'])           # "default"
print(d.get('x'))       # None
'x' in d                # False
list(d.keys())          # []
d.pop('x', "fallback")  # "fallback"     ← __missing__ NOT called

If you want consistent miss behavior across the whole API, __missing__ alone isn’t enough — you’d need to override get, __contains__, etc., too. Easier path: use defaultdict if the goal is “auto-populate on miss,” or wrap your dict access in a helper.

Interview angle

  • Q: “What’s __missing__?” — dict-subclass hook called by __getitem__ when the key is absent.
  • Q: “Why doesn’t d.get('x') trigger it?” — .get() has its own lookup logic; __missing__ is only called from [] access via __getitem__.
  • Follow-up: “How does defaultdict use it?” — calls factory, stores the result, returns it.
  • Follow-up: “Where is __missing__ useful in practice?” — format_map with safe placeholders, lazy caches, custom dict subclasses.

See stdlib/01_collections.md, 16_dict_get_falsy_default.md, 39_setdefault_evaluates_default.md.