都知道 Python 有强大的动态语言特性,得益于它“无处不对象”的语言设计思想
那么这些对象的源头来自哪里?这背后一定有它的出发点。
相信很多人一下就想到元类(metaclass
)那种复杂的东西,且慢,心急吃不了热豆腐
在深入研究元类编程之前,一些基本的但也是最重要的语言知识一定要弄清楚,否则会一头雾水
在此之前,明白 Python 中 type
与 object
的关系至关重要,它俩才是 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
以上关系可用下图来表示:
至此,对于文章一开头的八个输出就可以明确四个了,分别是
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 隐约有什么关系
其实它们的名字就已经告诉我们了,这里可用下图来描述:
最后还有一条很重要的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,就是:
那有没有第四条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'>
以上全部加入白板后如下:
– end –
参考资料