zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    工单系统(帮助中心)
    理解RESTful架构
    trace显示不出东西
    thinkphp
    在一个元素中查找子元素
    阻止表单元素失去焦点
    RelativeLayout不能调用measure去直接测量子元素
    兼容加载Xml字符串
    IE下载时提示无法下载,重试后成功
    借用layer让弹层不限制在iframe内部
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9615157.html
Copyright © 2011-2022 走看看