type()和isinstance()有什么区别?

1557 投票
8 回答
885540 浏览
提问于 2025-04-15 14:59

这两段代码有什么不同呢?

第一段代码使用了 type 这个函数:

import types

if type(a) is types.DictType:
    do_something()
if type(b) in types.StringTypes:
    do_something_else()

第二段代码使用了 isinstance 这个函数:

if isinstance(a, dict):
    do_something()
if isinstance(b, str) or isinstance(b, unicode):
    do_something_else()

8 个回答

132

Python中 isinstance()type() 的区别是什么?

使用

isinstance(obj, Base)

进行类型检查时,可以接受子类的实例和多个可能的基类:

isinstance(obj, (Base1, Base2))

而使用

type(obj) is Base

进行类型检查时,只支持指定的类型。


顺便提一下,使用 is 可能比使用

type(obj) == Base

更合适,因为类是单例的。

避免类型检查 - 使用多态(鸭子类型)

在Python中,通常你希望你的参数可以是任何类型,按预期处理它。如果对象的表现不符合预期,程序会抛出相应的错误。这种方式被称为多态,也叫做鸭子类型。

def function_of_duck(duck):
    duck.quack()
    duck.swim()

如果上面的代码能正常工作,我们可以假设我们的参数是只要像鸭子一样的东西。因此,我们可以传入其他实际是鸭子子类型的东西:

function_of_duck(mallard)

或者是表现得像鸭子的东西:

function_of_duck(object_that_quacks_and_swims_like_a_duck)

这样我们的代码依然可以正常工作。

不过,有些情况下,明确进行类型检查是有必要的。比如,你可能需要对不同类型的对象做一些合理的处理。例如,Pandas的DataFrame对象可以从字典 记录构建。在这种情况下,你的代码需要知道它接收到的参数是什么类型,以便能够正确处理。

所以,来回答这个问题:

Python中 isinstance()type() 的区别是什么?

让我来演示一下它们的区别:

type

假设你需要确保你的函数在接收到某种类型的参数时能有特定的行为(这是构造函数的常见用法)。如果你像这样检查类型:

def foo(data):
    '''accepts a dict to construct something, string support in future'''
    if type(data) is not dict:
        # we're only going to test for dicts for now
        raise ValueError('only dicts are supported for now')

如果我们尝试传入一个是 dict 子类的字典(如果我们希望我们的代码遵循 里斯科夫替换原则,那么子类型可以替代父类型),我们的代码就会出错!:

from collections import OrderedDict

foo(OrderedDict([('foo', 'bar'), ('fizz', 'buzz')]))

会抛出一个错误!

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in foo
ValueError: argument must be a dict

isinstance

但是如果我们使用 isinstance,就可以支持里斯科夫替换原则!:

def foo(a_dict):
    if not isinstance(a_dict, dict):
        raise ValueError('argument must be a dict')
    return a_dict

foo(OrderedDict([('foo', 'bar'), ('fizz', 'buzz')]))

返回 OrderedDict([('foo', 'bar'), ('fizz', 'buzz')])

抽象基类

实际上,我们可以做得更好。collections 提供了抽象基类,强制执行各种类型的最小协议。在我们的例子中,如果我们只期望 Mapping 协议,我们可以这样做,这样我们的代码会变得更加灵活:

from collections import Mapping

def foo(a_dict):
    if not isinstance(a_dict, Mapping):
        raise ValueError('argument must be a dict')
    return a_dict

对评论的回应:

需要注意的是,type可以用来检查多个类,使用 type(obj) in (A, B, C)

是的,你可以测试类型的相等性,但与其这样,不如使用多个基类来控制流程,除非你确实只允许那些类型:

isinstance(obj, (A, B, C))

再次强调,isinstance 支持可以替代父类的子类,而不会破坏程序,这个特性被称为里斯科夫替换原则。

更好的做法是,反转你的依赖关系,根本不检查特定类型。

结论

因此,由于我们希望支持子类的替代,在大多数情况下,我们应该避免使用 type 进行类型检查,而更倾向于使用 isinstance 进行类型检查,除非你真的需要知道一个实例的确切类。

445

这里有一个例子,展示了 isinstance 能做到的事情,而 type 做不到:

class Vehicle:
    pass

class Truck(Vehicle):
    pass

在这个例子中,一个 Truck 对象是一个 Vehicle,但是你会得到这样的结果:

isinstance(Vehicle(), Vehicle)  # returns True
type(Vehicle()) == Vehicle      # returns True
isinstance(Truck(), Vehicle)    # returns True
type(Truck()) == Vehicle        # returns False, and this probably won't be what you want.

换句话说,isinstance() 对于子类也是有效的。

你还可以查看:如何比较 Python 中对象的类型?

1524

简单来说,isinstance 可以处理继承关系(也就是说,一个子类的实例也是父类的实例),而检查 type 的相等性则不行(它要求类型完全一致,拒绝子类的实例)。

通常在 Python 中,你希望你的代码支持继承,因为继承非常方便。如果你的代码不允许其他代码使用继承,那就太糟糕了!所以,isinstance 比检查类型相等要好一些,因为它可以无缝支持继承。

不过,isinstance 也并不是说特别好,它只是比检查类型相等要“稍微好一点”。在 Python 中,通常推荐的做法是“鸭子类型”:你可以尝试把参数当作某种你想要的类型来使用,然后用 try/except 语句来捕获可能出现的异常。如果参数实际上不是那种类型,或者其他类型也不符合,你就可以在 except 里尝试其他方法(把参数当作其他类型来用)。

basestring 是一个特别的情况——它是一个内置类型,存在的唯一目的就是让你使用 isinstancestrunicode 都是 basestring 的子类)。字符串是序列(你可以遍历它们、索引它们、切片它们……),但通常你希望把它们当作“标量”类型来处理——把所有类型的字符串(以及其他一些不能遍历的标量类型)当作一种方式来处理,把所有容器(列表、集合、字典等)当作另一种方式来处理,这样就会比较方便,而 basestringisinstance 就帮助你实现了这一点。这个用法的整体结构大致是这样的:

if isinstance(x, basestring)
  return treatasscalar(x)
try:
  return treatasiter(iter(x))
except TypeError:
  return treatasscalar(x)

你可以说 basestring 是一个“抽象基类”(“ABC”)——它并不提供给子类具体的功能,而是作为一个“标记”存在,主要是为了和 isinstance 一起使用。这个概念在 Python 中越来越重要,因为 PEP 3119 引入了它的一个推广版本,并在 Python 2.6 和 3.0 中实现。

PEP 让我们明白,虽然 ABCs 可以替代鸭子类型,但通常没有强烈的需求去这样做(具体可以看 这里)。不过,在最近的 Python 版本中实现的 ABCs 确实提供了一些额外的好处:isinstance(和 issubclass)现在的含义不仅仅是“某个子类的实例”(特别是,任何类都可以“注册”到一个 ABC 上,这样它就会显示为子类,而它的实例也会被视为 ABC 的实例);而且 ABCs 还可以通过模板方法设计模式为实际的子类提供额外的便利(具体可以看 这里这里 [[第二部分]],了解更多关于模板方法设计模式的信息,无论是一般的还是特定于 Python 的,和 ABCs 无关)。

关于 Python 2.6 中 ABC 支持的底层机制,可以查看 这里;对于 3.1 版本,内容非常相似,可以查看 这里。在这两个版本中,标准库模块 collections(这是 3.1 版本——对于非常相似的 2.6 版本,可以查看 这里)提供了几个有用的 ABCs。

对于这个回答来说,关于 ABCs 的关键点是(除了相较于经典的 Python 替代方案混合类如 UserDict.DictMixin,它们为模板方法设计模式功能提供了更自然的放置方式),就是它们让 isinstance(和 issubclass)在 Python 2.6 及以后的版本中变得更加吸引人和普遍使用,而不是像 2.5 及之前那样。因此,相比之下,在最近的 Python 版本中,检查类型相等的做法变得更加糟糕了。

撰写回答