Python 中 type 与 object 的关系

都知道 Python 有强大的动态语言特性,得益于它“无处不对象”的语言设计思想

那么这些对象的源头来自哪里?这背后一定有它的出发点

相信很多人一下就想到元类(metaclass)那种复杂的东西,且慢,心急吃不了热豆腐

在深入研究元类编程之前,一些基本的但也是最重要的语言知识一定要弄清楚,否则会一头雾水

在此之前,明白 Python 中 typeobject 的关系至关重要,它俩才是 Python 中一切对象的出发点

先来看看这8个输出:

In [1]: isinstance(object, object)
Out[1]: True

In [2]: isinstance(object, type)
Out[2]: True

In [3]: isinstance(type, type)
Out[3]: True

In [4]: isinstance(type, object)
Out[4]: True

In [5]: issubclass(object, object)
Out[5]: True

In [6]: issubclass(object, type)
Out[6]: False

In [7]: issubclass(type, type)
Out[7]: True

In [8]: issubclass(type, object)
Out[8]: True

以上输出在 py2.x 和 py3.x 下都一样,如果都能明白为什么,那么 type 与 object 的关系也就容易弄懂了。

这两者的关系类似鸡和蛋的关系,究竟谁先出现?二者其实都不能独立,它们同时出现而互相依赖。

首先明白在面向对象概念中存在两种关系:

  • 父子关系(a kinf of),即继承关系,如 “猫是哺乳动物”,可以通过 __bases__ 属性查看某个类型继承的所有父类(返回一个元组)
  • 类型实例关系(an instance of),即类的实例化,如 “小白是猫”,可以通过 __class__ 属性或 type() 方法查看某个实例的所属类型

如果用实线(竖线)表示父子关系,用虚线(横线)表示类型实例关系,那么可以用下图来描述:

两种关系

假设有这么一张白板,分割成三部分,用来描述 Python 里面对象的关系:

白板

先来看看 type 和 object

在 Python 中,object 是父子关系的顶端,所有类型都是它的子类

type 是类型实例关系的顶端,所有类型都是它的实例

注意,不是所有对象都是 type 的实例,因为类型实例关系不具有传递性

object 是所有类的父类,而 type 是所有类的类

In [1]: object.__class__
Out[1]: type   # object 的类型是 type

In [2]: object.__bases__
Out[2]: ()     # object 没有父类,位于链条顶端

In [3]: type.__class__
Out[3]: type  # type 的类型是它本身

In [4]: type.__bases__
Out[4]: (object,)    # type 的父类是 object

以上关系可用下图来表示:

type 与 object 的关系

至此,对于文章一开头的八个输出就可以明确四个了,分别是

In [2]: isinstance(object, type)
Out[2]: True  # object 的类型是 type

In [3]: isinstance(type, type)
Out[3]: True   # type 的类型是它本身

In [6]: issubclass(object, type)
Out[6]: False  # object 的父类不是 type,它没有父类

In [8]: issubclass(type, object)
Out[8]: True # type 的父类是 object

那么其他四个如何解释?

In [1]: isinstance(object, object)
Out[1]: True

In [4]: isinstance(type, object)
Out[4]: True

In [5]: issubclass(object, object)
Out[5]: True

In [7]: issubclass(type, type)
Out[7]: True

这就要考虑到 isinstance() 遵守的 Dashed Arrow Up Rule

如果 X 是 A 的实例,且 A 是 B 的子类,那么 X 是 B 的 实例

>>> class B:
...     pass
...
>>> class A(B):
...     pass
...
>>> X = A()
>>> isinstance(X, A)
True
>>> issubclass(A, B)
True
>>> isinstance(X, B)
True

因此,由 isinstance(object, type) 和 issubclass(type, object),可以得出

In [1]: isinstance(object, object)
Out[1]: True

由 isinstance(type, type) 和 issubclass(type, object) 可以得出

In [4]: isinstance(type, object)
Out[4]: True

至于最后两条,对于 issubclass() 方法来说,每个类型都是它自身的子类(父类),因此也就有

In [5]: issubclass(object, object)
Out[5]: True

In [7]: issubclass(type, type)
Out[7]: True

文章到这里貌似就要结束了,但好戏还在后头

既然有 Dashed Arrow Up Rule,那就有 Dashed Arrow Down Rule

如果 A 是 B 的子类,且 B 是 M 的实例,那么 A 是 M 的实例

>>> class A(object):  
...     pass           
...                    
>>> isinstance(object, type) 
True                         
>>> issubclass(A, object)    
True                         
>>> isinstance(A, type)      
True

到这里你可能会觉得 Dashed Arrow Up Rule 和 Dashed Arrow Down Rule 隐约有什么关系

其实它们的名字就已经告诉我们了,这里可用下图来描述:

Up and Down

最后还有一条很重要的Rule,Combine Solid Arrows Rule:

如果 A 是 B 的子类,且 B 是 C 的子类,那么 A 也是 C 的子类

即继承关系具有传递性:

>>> class C:
...     pass
...
>>> class B(C):
...     pass
...
>>> class A(B):
...     pass
...
>>> issubclass(A, B)
True
>>> issubclass(B, C)
True
>>> issubclass(A, C)
True

总结以上三条Rule,就是:

Three Rules

那有没有第四条Rule,即类型实例关系也具有传递性呢(前面有提过)?

类型实例关系具有传递性吗?

验证一下:

>>> isinstance(int, type)
True
>>> isinstance(1, int)
True
>>> isinstance(1, type)
False

很明显,实例类型关系不具有传递性,但为什么继承关系可以传递而实例类型关系却不可以?

Python 对象一次只能有一种类型,如果实例类型关系具有传递性,那么就会出现一个对象同时对应两种不同的类型。

现在开始完善我们的白板,引入 list, dict, tuple 这些内置数据类型来看看(以下所有输出都是在 py3.x 解释器中):

>>> list.__bases__
(<class 'object'>,)  # py2.x 是 (<type 'object'>,)
>>> list.__class__
<class 'type'>  # py2.x 是 <type 'type'>


>>> dict.__bases__
(<class 'object'>,)
>>> dict.__class__
<class 'type'>


>>> tuple.__bases__
(<class 'object'>,)
>>> tuple.__class__
<class 'type'>

内置类型都继承于 object,且都是 type 的实例

再实例化一个list来看看:

>>> l = [1,2,3]
>>> l.__bases__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__bases__'
>>> l.__class__
<class 'list'>

实例化后的 l 对象没有 __bases__ 属性,也就是没有父类这一说法,它只是 list 的一个实例

以上全部加到白板上就是:

加入内置数据类型

注:该图没有完全按照前面三条 Rules 来画,只是按照 __bases__ 和 __class__ 的结果来画,以下同。

白板上除了 type 和 object,横向虚线代表实例,纵向实线代表继承

当我们自己去定个一个类并实例化它的时候,和上面的对象之间又是什么关系呢?

>>> class A:
...     pass
...
>>> a = A()
>>>
>>> A.__bases__
(<class 'object'>,)
>>> A.__class__
<class 'type'>
>>> a.__class__
<class '__main__.A'>
>>> a.__bases__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__bases__'

如我们所料,A 继承了 object 并成为 type 的一个实例,而 a 只是 A 的一个实例,没有继承任何父类

注意到object自己也是可以实例化的:

>>> ob = object()
>>> ob.__class__
<class 'object'>
>>> ob.__bases__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute '__bases__'

以上全部加入白板后如下:

自定义类

到这里就会发现 type 所在那一栏只有它一个,所以引入了 metaclass 用来扩展 type,这需要我们去继承 type

这就意味着我们可以自定义生成类的模板,达到动态生成类的目的(可见 Python 的动态特性非常完美):

>>> class M(type):
...     pass
...
>>> M.__class__
<class 'type'>    # 所有的类都是 type 的实例,包括自定义的元类
>>> M.__bases__
(<class 'type'>,)

有趣的是自定义的元类既是 type 的子类也是 type 的实例

那么如何实例化元类来生成一个新类呢? 可以直接 m = M() 吗?

>>> m = M()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type.__new__() takes exactly 3 arguments (0 given)

显然不行,缺少三个参数,具体是哪三个见StackOverflow一个关于元类的高票回答,这里就不再展开。

那如何优雅地实例化元类呢?

在 py3.x 下是直接在继承时声明:

>>> class T(metaclass=M):
...     pass
...
>>> T.__bases__
(<class 'object'>,)  # object 是所有类的父类
>>> T.__class__
<class '__main__.M'>

在 py2.x 下是定义一个 __metaclass__ 类属性:

>>> class T:
...     __metaclass__=M
...
>>> T.__class__
<class '__main__.M'>
>>> T.__bases__
(<type 'object'>,)

用 T 来生成一个实例看看:

>>> t = T()
>>> t.__bases__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'T' object has no attribute '__bases__'
>>> t.__class__
<class '__main__.T'>

以上全部加入白板后如下:

加入 metaclass

– end –

参考资料

文章目录
|