在做项目的 mypy strict 模式升级时,遇到一个微妙的类型选择:函数需要接受任意类型的输入,再用 isinstance 做防御性检查。参数该标 object 还是 Any?
看起来差不多,实际上差别很大。
一句话总结
object= 接受任何类型,但你必须先isinstance收窄才能用。mypy 会强制保护后续操作。Any= 完全关掉这个变量的类型检查。等于在每个访问点加了# type: ignore。
代码对比
用 object
| |
mypy 行为:
query_text.strip()在 isinstance 之前写 → ❌ 报错object has no attribute stripquery_text.strip()在 isinstance 之后写 → ✅ 安全,mypy 知道是str
用 Any
| |
Any 本质上是类型系统的逃生舱(escape hatch)。isinstance 收窄对 mypy 来说没有意义——因为它本来就不检查。
什么时候用哪个
| 场景 | 选择 | 原因 |
|---|---|---|
| 接受任意输入 + isinstance 守卫 + 收窄后使用 | object | isinstance 后获得类型保护 |
| SDK 回调、动态 JSON、无法确定的第三方返回值 | Any | 真的无法标注,需要逃生舱 |
**kwargs 动态字段 | Any | 字典值类型不确定 |
| Mixin / Protocol 边界 | Any | 类型系统表达不了 |
真实案例
升级 mypy strict 时,有几个函数的参数标了 str,mypy 的 warn_unreachable 报里面的 isinstance(x, str) 检查不可达(因为参数已经是 str 了)。最初的修复方案是删掉 isinstance 守卫——但这破坏了运行时防御,测试变红。
正确做法是把参数类型从 str 改成 object:
| |
这样既满足了 mypy strict(isinstance 检查可达),又保留了运行时防御(非 str 输入安全返回),还获得了 isinstance 后的类型保护。
深层原因
object 是 Python 类型体系的顶端类型(top type)——所有类型都是 object 的子类型。mypy 知道 object 只有 __init__、__str__ 等所有对象共有的方法,其他都要收窄。
Any 是渐进类型检查(gradual typing)的核心机制——它表示「这个边界处静态类型和动态类型交接」,mypy 会让 Any 污染传播:任何与 Any 交互的值都会被降级为 Any,类型保护就此中断。
| |
结论
如果你刚开了 strict 模式,请默认用 object。只在真正无法标注的边界(第三方 SDK、动态解析的 JSON)才用 Any。用 Any 的地方越多,strict 模式的价值就越低——等于花钱买了保险却不去理赔。