K 的一隅

Python Python 语言核心

type 和 isinstance 在问什么:对象的类型

type(x) 返回的是类型对象而非字符串;isinstance 与 type(x) is T 的语义差别;以及「一切皆对象」在 Python 里具体指什么。

7 分钟阅读 更新于 2026-07-28
本文目录

3"hi"len 在 Python 里都是对象:占内存、有身份、绑在一套行为(方法)上。问「它是什么类型」,运行时给出的答案不是字符串标签,而是一个叫 类型对象 的东西——intstrbuiltin_function_or_method 本身也是对象。typeisinstance 就是两条最常用的查询路径,但它们在问的问题并不完全一样。

上一篇用 class实例;实例诞生后,它的类型就固定为那个 class(或子类)对应的 类型对象。本篇回答两个实用问题:单独看一个值,它的类型对象是谁;在分支逻辑里,要不要把子类也算作“某种父类”。搞混这两点,容易出现“明明继承关系对,却进不了 if 分支”的 bug。

type(x):对象的类型对象

对一个实例 x 调用 type(x),返回的是它的类(类型对象),不是 "int" 这种文字:

python
n: int = 42
print(type(n))        # <class 'int'>
print(type(n) is int) # True

s: str = "hello"
print(type(s))        # <class 'str'>

类型对象可以像普通值一样赋给变量、放进容器,甚至参与比较:

python
T = int
values: list[object] = [1, "a", 3.14]
nums = [x for x in values if type(x) is T]
print(nums)  # [1]

type(x) is int 做的是身份比较x 的运行时类型是否恰好int 这个对象。子类实例不会通过:

python
class MyInt(int):
    pass

m = MyInt(7)
print(type(m))           # <class '__main__.MyInt'>
print(type(m) is int)    # False
print(isinstance(m, int))  # True

MyInt类型对象int 不是同一个对象,所以精确判断失败;但 isinstance 沿 MRO 看到 MyIntintobject,因此仍认为 m “属于”整数这一类。若你的 API 文档写“接受 int”,通常应使用 isinstance,除非你有意拒绝一切子类(例如序列化格式必须严格内置类型)。

isinstance:沿继承链问「算不算」

isinstance(obj, classinfo) 问的是:obj 是否是 classinfo 所表示类型的实例,包括子类classinfo 可以是单个类型,也可以是类型元组:

python
def describe(x: object) -> str:
    if isinstance(x, (int, float)):
        return "数值"
    if isinstance(x, str):
        return "文本"
    return "其他"


print(describe(3.14))   # 数值
print(describe("ok"))   # 文本
print(describe([]))     # 其他

type(x) is T 的对照:

写法子类实例典型用途
type(x) is T否(除非恰好是 T)只要精确类型、排除子类
isinstance(x, T)接口/契约式分支、多态

鸭子类型场景里,很多人更关心「有没有某行为」而不是继承树;但若你已经在用类层次,isinstance 通常比 type(x) is ... 更稳。

元组形式的 isinstance

isinstance(x, (A, B, C)) 等价于“是 A、B、C 任一类型的实例吗”,适合分支里合并多种合法输入:

python
def stringify(value: object) -> str:
    if isinstance(value, (bytes, bytearray)):
        return value.decode("utf-8")
    if isinstance(value, str):
        return value
    return str(value)


print(stringify(b"hi"))  # hi

注意第二个参数必须是 类型或类型元组isinstance(x, "str") 会报错,因为字符串不是类型对象。

类型对象也是对象

intstrlist 这些名字,指向的是 类型对象。它们由元类(一般是 type)创建,同样可以被 type() 查看:

python
print(type(int))       # <class 'type'>
print(type(type))      # <class 'type'>  —— type 是自己的类型
print(isinstance(int, type))  # True

「一切皆对象」在这里的含义是:数字、函数、类、模块、甚至 类型对象 本身,都是 heap 上的对象,由引用绑定。类型标注里的 int 与运行时的 int 类型对象是同一套名字体系的不同用法——标注给静态工具看,运行时 type(42) is int 给分支逻辑看。

函数、方法同样有自己的 类型对象functionmethod 等内置类型名),只是日常很少拿它们做分支:

python
def ping() -> None:
    pass


print(type(ping))                    # <class 'function'>
print(isinstance(ping, object))      # True —— 函数也是 object 的子类实例

type 的三参数形式(了解)

type 除了 type(obj),还可以 type(name, bases, dict) 动态创建类——返回一个新的 类型对象

python
Point = type("Point", (), {"x": 0, "y": 0})
p = Point()
print(type(p) is Point)  # True

这与 class Point: 语法糖等价于不同写法路径;读懂 traceback 里 <class 'type'> 相关栈帧时会用到。日常定义类仍推荐 class 关键字,可读性更好。

类型对象 当成“对一类行为的命名”也有帮助:list 不仅是一个名字,还携带 appendextend 等方法表;type(x) 告诉你当前对象参照哪张表。标注 x: list[str] 里的 listtype([]) 返回的对象是同一概念在不同阶段的用法。

何时用哪一个

  • 精确匹配、排除子类type(x) is list,例如只想处理内置 list、不要自定义子类。
  • 接受子类或多类型isinstance(x, (list, tuple))
  • 不要用 type(x) == int== 可能在某些边缘场景与 is 行为不同;精确类型判断惯用 is
python
class Stack(list):
    pass

s = Stack([1, 2])
if type(s) is list:
    print("纯 list")      # 不执行
if isinstance(s, list):
    print("list 或子类")  # 执行

None、bool 与单例

None 只有一个对象,type(None)NoneTypeTrue / Falsebool 的实例,而 bool 又是 int 的子类——因此 isinstance(True, int) 为真,但 type(True) is int 为假:

python
print(type(None))           # <class 'NoneType'>
print(isinstance(True, int))  # True
print(type(True) is int)      # False

写守卫条件时,若业务上要把 bool 从数值里拆出去,需要显式分支,不能假设 isinstance(x, int) 就只剩整数。

抽象基类与 register(略提)

标准库 collections.abctyping 里有些抽象基类允许“虚拟子类”注册,使得 isinstance 在没有继承关系时也可能为真。这是高级用法,日常业务类很少碰到;若你看到 isinstance(x, Iterable) 对自定义类成立,却找不到继承声明,可以去查是否调用了 register。大多数情况下,isinstance 仍是在 MRO 上找 类型对象

和上一篇、下一篇的衔接

上一篇的 class 定义出类型;Counter() 造出 实例type(实例) 就是那个 class。下一篇会讲 __new__ / __init__ 在创建时谁先谁后——类型在对象诞生那一刻就已经确定,__init__ 只填充状态,不改 type() 的结果。

调试类型相关 bug 时,优先打印 type(x)x.__class__(通常一致),再决定用 isinstance 还是精确 is。JSON 解析、网络 payload 等边界处,常同时出现 int/floatstr/bytes,用元组形式的 isinstance 比连环 or 更清晰。

搞清 type 返回 类型对象isinstance 沿 MRO 做成员判断,读标准库源码(大量 isinstance 分支)和写自己的类型守卫时会顺手很多。需要排除子类时用 type(x) is T;需要尊重继承时用 isinstance。两者不要混写成 type(x) == T,身份判断惯用 is

小结对照

问题首选工具备注
这个值的类型对象是谁type(x)返回 类型对象,不是字符串
算不算某类(含子类)isinstance(x, T)可传类型元组
只要精确内置类型type(x) is T排除子类
类本身是什么类型type(int)type类型对象 也是 object

记住:type 回答“你是谁”,isinstance 回答“你算不算这一类家族成员”。对象模型里二者互补,不互相替代。

运行时类型与静态标注可以不一致:标注 list[int] 不会在运行时拦住你 append 字符串;守卫分支仍要靠 isinstance 或自己写的校验。类型对象 是运行时概念,标注是开发期辅助——别指望只靠 type 检查就能替代测试或静态分析工具。

读 traceback 时,TypeError: isinstance() arg 2 must be a type... 多半是把字符串或普通对象当成了 类型对象 传给 isinstanceAttributeError 出现在 type(x).foo 则可能是把非 类型对象 当成了类。养成“打印 type(x)<class '...'>”的习惯,能少绕很多弯。

x.__class__type(x) 在普通实例上结果相同;极少数代理对象可能二者不同,属于高级话题。日常调试任选其一即可,与 isinstance 联用时优先相信 isinstance 的继承语义。写单元测试断言类型时,也优先 isinstance,避免子类合法扩展被误杀。对外 API 文档若写“参数必须是 list”,实现里用 isinstance(arg, list) 通常比 type(arg) is list 更贴近读者预期。