回溯指向表达式,不是原因
KeyError 回溯指向 data['tenant_id'],但原因可能是上游服务改了字段名、YAML 缩进不同,或某个分支构造字典时漏了键。查找前打印可用键和对象类型。缺失键是症状,为什么缺失才是缺陷。
payload = {"tenant": "acme", "user": None}
tenant = payload["tenant_id"] # KeyError: 'tenant_id'
键存在但值为 None 时行为不同:payload["user"]["name"] 抛出 TypeError 而不是 KeyError,因为 None 不可下标。要分别处理这两种情况。
选择正确的缺失策略
缺失无效时,在边界处校验并抛出清晰错误:
REQUIRED = ("tenant_id", "user_id")
missing = [key for key in REQUIRED if key not in payload]
if missing:
raise ValueError(f"missing required fields: {', '.join(missing)}")
缺失有效时,使用 dict.get(key, default) 或 collections.defaultdict,并在类型和测试中明确默认值。不要把通用 {} 默认值扩散到代码各处,它会掩盖畸形负载,并在远离源头的位置产生错误。
from collections import defaultdict
counts = defaultdict(int)
counts["errors"] += 1
当所有缺失键都有相同且有意义的默认值时,defaultdict 合适。对必需业务字段不合适,缺失应当响亮失败。
校验形状,而不只是一个键
如果字典来自 JSON,使用 Pydantic 等模式校验器或手写检查,把契约显式化。在边界校验一次,把类型化对象传给后续代码。这样能把散落的 KeyError 变成带字段路径的结构化校验错误。对配置,应在启动时因缺少必需环境变量失败,而不是等到第一个使用它的请求。
修复并验证
修复方式是边界校验必填键,并为可选键选择显式策略。用测试验证:从负载中删除键,断言清晰的校验错误;再测试键存在但值为 None。重跑原始失败输入,确认得到领域错误而不是回溯。