__missing__仅在继承dict且通过d[key]访问缺失键时触发,不干预.get()、in等操作;必须返回值或显式抛异常,否则仍报KeyError。

直接说结论: __missing__ 不是让字典“自动补值”的魔法钩子,而是仅在 dict.__getitem__()(即方括号 [] 访问)失败、且类继承自 dict 时才触发的兜底机制;它不干预 .get()、in、.keys() 等其他操作。
为什么 __missing__ 没生效?常见触发条件漏了
很多人写完 __missing__ 却发现根本没调用,问题几乎都出在继承关系或访问方式上:
- 必须显式继承
dict(或collections.UserDict),不能只继承object再手动实现__getitem__ - 只对
obj[key]报KeyError时触发;obj.get(key)、key in obj、obj.setdefault(key, default)都绕过它 -
__missing__必须返回值(或显式raise),不能只打印日志就结束——否则仍抛KeyError
正确写法示例:
class DefaultingDict(dict):
def __missing__(self, key):
self[key] = f"missing_{key}" # 自动设值
return self[key]
d = DefaultingDict()
print(d["foo"]) # 输出 "missing_foo",且 d 现在含 {"foo": "missing_foo"}
__missing__ 和 collections.defaultdict 的本质区别
二者目标相似,但机制和适用场景不同:
立即学习“Python免费学习笔记(深入)”;
-
defaultdict在__getitem__内部直接调用工厂函数生成默认值,不依赖__missing__;它的__missing__是内置实现,用户不可覆盖 - 自定义
dict子类的__missing__更灵活:可读取外部状态、查数据库、做缓存计算,甚至根据 key 类型分支处理 - 注意性能:如果
__missing__里做了耗时操作(如网络请求),每次未命中都会阻塞;而defaultdict的工厂函数通常轻量
比如需要按 key 前缀动态生成默认值:
class PrefixDefaultDict(dict):
def __missing__(self, key):
if key.startswith("user_"):
return {"name": "anonymous", "score": 0}
elif key.startswith("cfg_"):
return {}
raise KeyError(key)
容易被忽略的兼容性陷阱
__missing__ 是 Python 2.5+ 引入的,但某些旧环境(如部分嵌入式 Python 或极老版本)可能不支持;更重要的是它和第三方库的交互风险:
- 很多 ORM 或序列化库(如
json.dumps())内部用.keys()+__getitem__遍历,若__missing__改变了字典内容(如自动插入键),会导致遍历结果意外变化 - 使用
copy.copy()或dict(d)复制时,新字典不会继承原字典的__missing__行为,除非显式传入类构造器 - PyPy 下行为一致,但某些 C 扩展模块(如
ujson)可能绕过 Python 层的__missing__,直接调用底层 dict API
真正要用好 __missing__,得清楚它只是一次性兜底,不是全局默认策略;任何副作用(比如写入、IO、状态变更)都得考虑是否在重复访问时产生意料外的累积效果。


















