直接用 d['key'] 访问不存在的键一定会抛 keyerror,这是设计契约而非缺陷;最稳妥日常选择是 dict.get(),它不修改原字典、不抛异常、支持默认值,且第二个参数明确表达“可能缺失”的意图。

直接用 d['key'] 访问不存在的键,一定会抛 KeyError ——这不是缺陷,是设计契约:字典要求你明确处理缺失,而不是静默容忍。
用 dict.get() 替代方括号访问是最稳妥的日常选择
它不改变原字典、不抛异常、语义清晰,且支持默认值。关键点在于第二个参数不是“可选”,而是你主动声明“这里可能没有”的信号。
-
d.get('name')返回None,适合后续做is None判断 -
d.get('score', 0)避免None + 1报TypeError -
d.get('tags', [])每次都返回新列表,不能用于原地追加;若需“有则取、无则设空列表”,改用setdefault('tags', []) - 默认值可以是任意类型:字符串、数字、函数调用(如
d.get('retry', max_retries())),但避免传可变对象字面量(如[]或{})作默认值——除非你清楚它每次都是新实例
嵌套字典访问时,.get(key, {}) 必须写满每层默认值
链式调用 config.get('db').get('host') 是危险的:中间任意一层返回 None,后续 .get() 就会触发 AttributeError,而不是你预期的安静 fallback。
- 正确写法:
config.get('db', {}).get('host', 'localhost') - 如果嵌套层级深(比如
a['b']['c']['d']),建议拆成多步并检查每层返回值是否为 dict 类型,或改用functools.reduce(lambda d, k: d.get(k) if isinstance(d, dict) else None, keys, data) - 不要依赖
json.loads(..., object_hook=SimpleNamespace)后用点号访问——它无法处理None中间节点,且SimpleNamespace实例不可变,无法动态增删属性
需要“查无则设默认值”时,优先用 setdefault() 而非 get()
setdefault() 和 get() 行为差异极小,但语义完全不同:前者会**修改原字典**,后者纯读取。
-
d.setdefault('count', 0):键存在返回值;不存在则插入'count': 0并返回 0 -
d.setdefault('items', []).append(x)是安全的“获取或新建列表后追加”惯用法 - 注意副作用:多次调用
setdefault('key', expensive_func())会导致expensive_func()多次执行(因为默认值在每次调用时求值),应先算好再传入
真要捕获 KeyError,就别让它跨层模糊
嵌套访问中用 try/except KeyError 很难定位到底是哪一层出问题,调试成本高,也不利于单元测试覆盖。
- 单层访问(如解析 API 返回的扁平字段)可以用
try/except,但建议只在需要差异化错误日志或重试逻辑时才用 - 避免
except KeyError:不带具体键名——你无法区分是'user_id'缺失还是'avatar_url'缺失 - 更推荐显式检查:
if 'required_field' not in d: raise ValueError("missing required field"),把业务约束提前暴露
真正容易被忽略的不是“键不存在”,而是“键存在但值为 None 或空字典”,比如 resp.get('data', {}) 返回的是空 dict,接着你又调 .get('items'),结果拿到 None——这不会报 KeyError,但后续操作照样崩。防御性编程得从数据结构契约开始,不是只防异常。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











