Python虚拟机类机制之对象模型(一)

Python对象模型

在Python2.2之前,Python中存在着一个巨大的裂缝,就是Python的内置类type,比如:int和dict,这些内置类与程序员在Python中自定义的类并不是同一级别的类。比如说,程序员定义了class A,又定义了class B,B可以继承于A,这是理所当然的。但是,Python的内置类却不能被继承。程序员不能创建一个类继承于int或者dict。于是,Python的开发者在Python2.2中花费了巨大的精力填补了内置类和用户自定义class之间的鸿沟,使得两者能够在概念上实现了完全一致。这种统一后的类型机制,称之为new style class机制

在面向对象的理论中,有两个核心的概念:类和对象。在Python中也实现了这两个概念,但是在Python中,所有的东西都是对象,即便是类也不例外。现在,我们对Python中的类机制建模

Python2.2之前,Python中实际上存在三类对象:

  • type对象:表示Python内置类型
  • class对象:表示Python程序员自定义的类型
  • instance对象(实例对象):表示由class对象经实例化所创建的实例

在Python2.2之后,内置类type和class对象已经统一了,我们采用一种表达形式,我们采用<class A>来表示名为A的class对象,而对于instance对象,则采用<instance a>来表示名为a的instance对象

同时,我们将采用术语type来表示“类型”(注意,不是类型对象)这个概念。比如对于“实例对象a的类型是A”这样的说法,我们就可用“实例对象a的type是A”来表达。当然,术语class在某些情况下也表示“类型”,比如我们会采用“定义一个名为A的class”或“class A”这样的说法。但是当我们使用“class对象”时,就与“class”有完全不同的意义了。“class”表示“类”或类型的概念,而“class对象”表示这个概念在Python中的实现

对象间的关系

在Python的三种对象之间,存在着两种关系

  • is-kind-of:这种关系对应于面向对象的基类和子类之间的关系
  • is-instance-of:这种关系对应于面向对象中类与实例之间的关系

考虑下面的Python代码:

class A(object):
    pass
 
 
a = A()

  

其中包含三个对象:object(class对象)、A(class对象)、a(instance对象),object和A之间存在is-kind-of关系,即A是object的子类,而a和A之间存在is-instance-of关系,即a是A的一个实例,而a和object之间也存在is-instance-of关系,即a也是object的一个实例

Python提供了一些方法可以用于探测实例对象、类型对象之间的关系:

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

  

从a.__base__的结果可以看出,并不是所有的对象都拥有is-kind-of关系。因为is-kind-of关系对应基类和子类的关系,只能在classs对象和class对象之间存在,而a是instance对象,显然不能拥有这种关系。图1-1更加形象和清晰地展现了这三种对象之间的关系

图1-1   对象关系图

<type 'type'>和<type 'object'>

将object和A放在一起是因为它们一个共同点,就是它们的type都是<type 'type'>。<type 'type'>属于Python中的一种特殊的class对象,这种特殊的class对象能够成为其他class对象的type。这种特殊的class对象我们称之为metaclass对象

Python中还有一个特殊的class对象——<type 'object'>,在Python中,任何一个class都必须直接和间接继承自object,这个object可以视为万物之母 

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

  

图1-2   探测对象之间的关系

 Python中的对象分为class对象和instance对象,图1-2中间那一列既是class对象,也是instance对象,说它是class对象,因为它可以通过实例化的动作创建出instance对象,说它是instance对象,因为它确实是metaclass对象(即<type 'type'>)经过实例化得到的。图1-2也显示出class对象和metaclass对象之间存在is-instance-of关系

现在,我们总结一下:

  • 在Python中,任何一个对象都有一个type,可以通过__class__属性获得。任何一个instance对象的type都是一个class对象,而任何一个class对象的type都是metaclass对象。在大多数情况下这个metaclass都是<type 'type'>,在Python的内部,它实际上对应的就是PyType_Type
  • 在Python中,任何一个class对象都直接或间接与<type 'object'>对象之间存在is-kind-of关系,包括<type 'type'>。在Python内部,<type 'object'>对应的是PyBaseObject_Type

从type对象到class对象

考虑下面的代码:

class MyInt(int):
    def __add__(self, other):
        return int.__add__(self, other) + 10
 
 
a = MyInt(1)
b = MyInt(2)
print(a + b)

  

毫无疑问,这里会打印出13,当虚拟机调用MyInt.__add__时,还会再调用int_.__add__。当调用int_.__add__时,会先找到PyInt_Type内置type中的tp_as_number指针,这里的tp_as_number指针是指向int_as_number,然后再调用int_as_number的nb_add域,这里是int_add。通过int_add来完成int加法的操作

intobject.c

PyTypeObject PyInt_Type = {
	……
	&int_as_number,				/* tp_as_number */
	……
};

static PyNumberMethods int_as_number = {
	(binaryfunc)int_add,	/*nb_add*/
	……
};

  

但是有个问题,Python虚拟机该怎么从int.__add__得到要调用的是PyInt_Type.tp_as_number.nb_add呢?这就是Python2.2之前内置类型不能被继承的原因了,因为Python没有在type中寻找某个属性的机制

这里先以<type 'int'>(也就是PyInt_Type)为例,图1-2中先给出Python中class对象的一个粗略的图示,从中可以看到Python2.2之后是如何解决属性寻找机制的

图1-2   粗略的<type 'int'>示意图

当Python虚拟机需要调用int.__add__时,它可以到符号int对应的class对象PyInt_Type的tp_dict指向的dict对象中查找符号__add__对应的操作,并调用该操作,从而完成对int.__add__的调用

图1-2仅仅是一个粗略的示意图,实际上在tp_dict中与符号__add__对应的对象虽然与nb_add有关系,但并不是直接指向nb_add的,我们可以说调用__add__对应的操作,那个这个对象是不是我们先前说过的PyFunctionObject对象呢?毕竟__add__是一个函数,调用函数对象很合理呀。但实际上,在Python中,不仅只有函数可以被调用,甚至是对象也有可能被调用

对象的可调用性,即callable,只要一个对象对应的class对象中实现了__call__操作(更确切地说,在Python内部的PyTypeObject中,tp_call不为空)那么这个对象就是一个可调用的对象,换句话说,在Python中,所谓“调用”就是执行对象的type所对应的class对象的tp_call操作,如下: 

>>> class A(object):
...     def __call__(self):
...         print("Hello Python")
...
>>> a = A()
>>> a()
Hello Python

  

 在Python内部,通过一个叫PyObject_Call的函数完成对instance对象进行调用操作,从而调用变量a的__call__方法

下面展示一个不可调用的例子,从这里可以看到,PyObject_Call对任何对象都会按部就班地试图完成这个“调用”操作,如果传进来的对象是可调用的,那就顺利完成,反之则抛出异常。显然,在调用函数f时一定会抛出异常

>>> def f():
...     i = 1
...     i()
...
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f
TypeError: 'int' object is not callable

  

从这里可以看出,一个对象是否可调用并不是在编译期就能确定的,必须是在运行时才能在PyObject_CallFunctionObjArgs中确定

从Python2.2开始,Python在启动时,会对类型系统(对象模型)进行初始化的动作。这个初始化的动作会动态地在内置类型对应的PyObjectType中填充一些东西,其中也包括tp_dict域,从而完成内置类型从type对象到class对象的转变。这个对类型系统进行初始化的动作从_Py_ReadyTypes开始

在_Py_ReadyTypes中,会调用PyType_Ready对class对象进行初始化。实际上,PyType_Ready仅仅是属于对class对象进行初始化这个动作的一部分,它处理的不光是Python的内置类型,还会处理用户自定义的类型。以内置类型list和自定义类型A来说明内置类型和用户自定义类型在初始化上的区别,list对应的class对象PyList_Type在Python启动后已经作为全局对象存在了,需要的仅仅是完善,而A对应的class对象则不存在,需要申请内存,并创建、初始化整个动作序列。所以对于list来说,初始化就剩下PyType_Ready了,而对于A来说,PyType_Ready仅仅是一部分

之前提到<type 'type'>是一个非常特殊的class对象,那么对PyType_Ready的考察就以它作为参数吧。<type 'type'>实际上对应着PyType_Type,所以对PyType_Ready的剖析就从PyType_Ready(&PyType_Type)开始

处理基类和type信息

typeobject.c 

int PyType_Ready(PyTypeObject *type)
{
    PyObject *dict, *bases;
    PyTypeObject *base;
    Py_ssize_t i, n;
 
    if (type->tp_flags & Py_TPFLAGS_READY) {
        assert(type->tp_dict != NULL);
        return 0;
    }
    assert((type->tp_flags & Py_TPFLAGS_READYING) == 0);
 
    type->tp_flags |= Py_TPFLAGS_READYING;
 
 
    //[1]:尝试获得type的tp_base中指定的基类
    base = type->tp_base;
    if (base == NULL && type != &PyBaseObject_Type) {
        base = type->tp_base = &PyBaseObject_Type;
        Py_INCREF(base);
    }
 
    //[2]:如果基类没有初始化,先初始化基类
    if (base && base->tp_dict == NULL) {
        if (PyType_Ready(base) < 0)
            goto error;
    }
 
    //[3]:设置type信息
    if (type->ob_type == NULL && base != NULL)
        type->ob_type = base->ob_type;
 
    ……
}

  

在[1]处,Python虚拟机会尝试获得待初始化的type的基类,这里的type是PyType_Ready中的参数名,也表示其对应的class对象。这个信息是在PyTypeObject.tp_base中指定的,表1-1列出了一些内置class对象的tp_base信息:

表1-2
class对象 基类信息
PyType_Type NULL
PyInt_Type NULL
PyBool_Type &PyInt_Type

 

对于指定了tp_base的内置class对象,当然就使用指定的基类,而对于没有指定tp_base的内置class对象,Python将为其指定一个默认的基类:PyBaseObject_Type,也就是<type 'object'>。所以这里可以看到,Python所有class对象都是直接或间接以<type 'object'>作为基类。而我们正在考察的PyType_Type的tp_base没有指定基类,所以它的基类就成了<type 'object'>

在获得基类后,会在PyType_Ready中的[2]处判断基类是否已初始化,如果没有,需要先对基类进行初始化。可以看到,判断初始化是否完成的条件是base->tp_dict是否为NULL,这符合之前对初始化的描述,初始化的一部分工作就是对tp_dict进行填充

在PyType_Ready的[3]处,设置了class对象的ob_type信息,实际上这个ob_type信息也就是对象的__class__将返回的信息。更进一步说,这里设置的ob_type就是metaclass。实际上,Python虚拟机是将基类的metaclass作为子类的metaclass。对于这里考察的PyType_Type来说,其metaclass正是<type 'object'>的metaclass,而在PyBaseObject_Type的定义中我们可以看到其ob_type被设置为PyType_Type

处理基类列表

接下来,Python虚拟机将处理类型的基类列表,因为Python支持多重继承,所以每个Python的class对象都会有一个基类列表

typeobject.c

int PyType_Ready(PyTypeObject *type)
{
    PyObject *dict, *bases;
    PyTypeObject *base;
    Py_ssize_t i, n;
    ……
    //尝试获得type中tp_base中指定基类
    base = type->tp_base;
    if (base == NULL && type != &PyBaseObject_Type) {
        base = type->tp_base = &PyBaseObject_Type;
        Py_INCREF(base);
    }
    ……
    //处理bases:基类列表
    bases = type->tp_bases;
    if (bases == NULL) {
        //如果base为空,根据base的情况设定bases
        if (base == NULL)
            bases = PyTuple_New(0);
        else
            bases = PyTuple_Pack(1, base);
        if (bases == NULL)
            goto error;
        type->tp_bases = bases;
    }
    ……
}

  

对于PyBaseObject_Type来说,其tp_bases为NULL,而其base也为NULL,所以它的基类列表就是一个空的tupple对象,这也符合之前我们打印object.__bases__的结果

而对于PyType_Type和其他类型,比如PyInt_Type来说,虽然tp_bases为NULL,但base不为NULL,而是&PyBaseObject_Type,所以它们的基类列表不为NULL,都包含一个PyBaseObject_Type

posted @ 2018-09-09 21:51  北洛  阅读(751)  评论(0编辑  收藏  举报