Python虚拟机类机制之自定义class(四)

用户自定义classpython

在本章中,咱们将研究对用户自定义class的剖析,在demo1.py中,咱们将研究单个class的实现,因此在这里并无关于继承及多态的讨论。然而在demo1.py中,咱们看到了许多类的内容,其中包括类的定义、类的构造函数、对象的实例化、类成员函数的调用等编程

demo1.pyapp

class A(object):
    name = "Python"
 
    def __init__(self):
        print("A::__init__")
 
    def f(self):
        print("A::f")
 
    def g(self, aValue):
        self.value = aValue
        print(self.value)
 
 
a = A()
a.f()
a.g(10)

  

咱们都知道,对于一个包含函数定义的Python源文件,在Python源文件编译后,会获得一个与源文件对应的PyCodeObject对象A,而与函数对应的PyCodeObject对象B则存储在A的co_consts变量中。那么对于包含类的Python源文件,编译以后的结果又如何呢?编程语言

>>> source = open("demo1.py").read()
>>> co = compile(source, "demo1.py", "exec")
>>> co.co_consts
('A', <code object A at 0x7f1048929dc8, file "demo1.py", line 1>, 10, None)
>>> A_co = co.co_consts[1]
>>> A_co.co_consts
('Python', <code object __init__ at 0x7f1048929648, file "demo1.py", line 4>, <code object f at 0x7f1048929918, file "demo1.py", line 7>, <code object g at 0x7f1048929af8, file "demo1.py", line 10>)
>>> A_co.co_names
('__name__', '__module__', 'name', '__init__', 'f', 'g')

  

能够看到,class A会编译成一个PyCodeObject,存放在源文件code的co_consts变量中,而class A的函数也会编译成PyCodeObject,存放在对A对应的PyCodeObject中函数

class的动态元信息布局

所谓的class的元信息就是指关于class的信息,好比说class的名称,它所拥有的属性、方法、该class实例化时要为实例对象申请的内存空间大小等。对于demo1.py中所定义的class A来讲,咱们必需要知道这样的信息:class A中,有一个符号f,这个f对应了一个函数,还有一个符号g,也对应一个函数。有了这些关于A的元信息,才能建立A的class对象。元信息在编程语言中是一个很是重要的概念,正是有了这个东西,Java、C#的一些初级的诸如反射(Reflection)等动态特性才有可能获得实现。在之后的剖析中能够看到,Python中的元信息概念被发挥到淋漓尽致,于是Python也提供了Java、C#等语言所没有的高度灵活的动态性ui

如今,咱们能够解释一下demo1.py所对应的字节码,看一下关于class A的字节码是长什么样的?spa

>>> source = open("demo1.py").read()
>>> co = compile(source, "demo1.py", "exec")
>>> import dis
>>> dis.dis(co)
  1           0 LOAD_CONST               0 ('A')
              3 LOAD_NAME                0 (object)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               1 (<code object A at 0x7f91e6ec7dc8, file "demo1.py", line 1>)
             12 MAKE_FUNCTION            0
             15 CALL_FUNCTION            0
             18 BUILD_CLASS         
             19 STORE_NAME               1 (A)

 15          22 LOAD_NAME                1 (A)
             25 CALL_FUNCTION            0
             28 STORE_NAME               2 (a)

 16          31 LOAD_NAME                2 (a)
             34 LOAD_ATTR                3 (f)
             37 CALL_FUNCTION            0
             40 POP_TOP             

 17          41 LOAD_NAME                2 (a)
             44 LOAD_ATTR                4 (g)
             47 LOAD_CONST               2 (10)
             50 CALL_FUNCTION            1
             53 POP_TOP             
             54 LOAD_CONST               3 (None)
             57 RETURN_VALUE  

  

咱们单独把class A相关的字节码指令提取出来命令行

0   LOAD_CONST               0 ('A')
3   LOAD_NAME                0 (object)
6   BUILD_TUPLE              1
9   LOAD_CONST               1 (<code object A at 0x7f1048929dc8, file "demo1.py", line 1>)
12  MAKE_FUNCTION            0
15  CALL_FUNCTION            0
18  BUILD_CLASS        
19  STORE_NAME               1 (A)

  

如今,咱们能够开始分析class A是如何执行的了:code

首先执行"0 LOAD_CONST   0"指令将类A的名称压入到运行时栈中,而接下来的LOAD_NAME指令和BUILD_TUPPLE指令是一个很是关键的点,这两条指令将基于类A的全部基类建立一个基类列表,固然这里只有一个名为object的基类。随后,Python虚拟机经过"9 LOAD_CONST   1"指令将与A对应的PyCodeObject压入到运行时栈中,并经过MAKE_FUNCTION指令建立一个PyFunctiobObject对象。在这些操做完成以后,咱们来看一看这时的运行时栈

图1-1   MAKE_FUNCTION指令完成后的运行时栈

以后,Python虚拟机开始执行"15 CALL_FUNCTION   0"指令。根据函数机制那一章的分析,咱们知道调用CALL_FUNCTION会建立一个新的PyFrameObject对象,并开始执行这个PyFrameObject对象中所包含的字节码序列,很显然,这些字节码序列来自运行时栈中那个PyFunctiobObject对象。参考上面的描述,咱们能够发现,这段字节码序列实际就是来自与A对应的PyCodeObject对象。换句话说,如今Python虚拟机所面对的目标从与demo1.py对应的字节码序列转换到与class A对应的字节码序列

>>> dis.dis(A_co)
  1           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)
 
  2           6 LOAD_CONST               0 ('Python')
              9 STORE_NAME               2 (name)
 
  4          12 LOAD_CONST               1 (<code object __init__ at 0x7f1048929648, file "demo1.py", line 4>)
             15 MAKE_FUNCTION            0
             18 STORE_NAME               3 (__init__)
 
  7          21 LOAD_CONST               2 (<code object f at 0x7f1048929918, file "demo1.py", line 7>)
             24 MAKE_FUNCTION            0
             27 STORE_NAME               4 (f)
 
 10          30 LOAD_CONST               3 (<code object g at 0x7f1048929af8, file "demo1.py", line 10>)
             33 MAKE_FUNCTION            0
             36 STORE_NAME               5 (g)
             39 LOAD_LOCALS        
             40 RETURN_VALUE 

  

Python在执行源文件的CALL_FUNCTION中,实际上只执行了一个赋值语句和3个def语句,建立了3个PyFunctionObject对象

开始的LOAD_NAME和STORE_NAME将符号__module__和全局名字空间中符号__name__对应的值__main__关联起来,并放入到local名字空间(PyFrameObject对象的f_locals)中。须要说明的是,这里的函数机制与以前有所不一样,在前面执行"15 CALL_FUNCTION   0"指令,建立新的PyFrameObject对象时,PyFrameObject中的f_locals被建立了,并指向一个PyDictObject对象,而在函数机制中f_locals是被设置为NULL,函数机制中局部变量是以一种位置参数的形式存放在了运行时栈前面的那段内存

接着,Python虚拟机连续执行3个(LOAD_CONST、MAKE_FUNCTION、STORE_NAME)指令序列对,每一个指令序列都会建立一个与类中成员函数对应的PyFunctiobObject对象,经过STORE_NAME存入到local名字空间中

回头想一想,这一路下来,咱们好像建立很多东西,但目前为止,建立的有用的东西都被放到local名字空间中,这里面存的偏偏是最重要的东西——class A的元信息

如今有动态元信息,那么必然会有静态元信息。关于这两者的区别,后面还会讨论

既然class A的动态元信息建立完毕,那咱们是否是应该要拿到A的class对象?因而,咱们开始后退,退出当前的class A的PyFrameObject,即栈帧,回到原先源文件所对应的PyFrameObject中,可是在回退以前,咱们必需要把当前栈帧class A的f_locals带走,否则class A的动态元信息建立了等于没有建立,咱们依旧不知道A有几个变量?有几个函数?

ceval.c

case LOAD_LOCALS:
    if ((x = f->f_locals) != NULL) {
        Py_INCREF(x);
        PUSH(x);
        continue;
    }
    PyErr_SetString(PyExc_SystemError, "no locals");
    break;

  

LOAD_LOCALS将f_locals压入运行时栈中,随后的RETURN_VALUE指令将运行时栈的f_locals返回给上一级的栈帧。这时候,咱们又回到CALL_FUNCTION,CALL_FUNCTION得到class A的f_locals后,将其压入运行时栈,如今的运行时栈如图1-2

图1-2   CALL_FUNCTION指令完成后的运行时栈 

咱们能够在call_function的实现代码中打印PyDictObject对象的代码,以观察返回的对象,咱们针对类名为A的class对象打印其返回的f_locals,可能会有人以为有点奇怪,这里的类名竟然是从一个函数中获取的?咱们都知道,func是一个PyFuncObject类型的对象,能够经过PyEval_GetFuncName获取其函数的名字,可是在调用call_function时类名也保存在PyFuncObject中的func_name吗?是的,没错。但别忘了,既然class A语句的执行都是以调用函数的形式来生成,那为何这个函数的函数名不能做为类名呢?

static PyObject *call_function(PyObject ***pp_stack, int oparg)
{
	……
	PyObject *func = *pfunc;
	PyObject *x, *w;
	……
	while ((*pp_stack) > pfunc) {
		w = EXT_POP(*pp_stack);
		Py_DECREF(w);
		PCALL(PCALL_POP);
	}
	//func_name即为类名,x即为上个栈帧所返回的f_locals
	char *func_name = PyEval_GetFuncName(func);
	if (strcmp(func_name, "A") == 0) {
		PyObject *std = PySys_GetObject("stdout");
		PyFile_WriteObject(x, std, Py_PRINT_RAW);
		printf("\n");
	}
	return x;
}

  

从新编译并在Python命令行执行以下class A,会进入以前咱们特定的if分支中并打印f_locals

>>> class A(object):
...     a = 1
...     d = {1: "Robert", 2: "Python"}
...     def f(self):
...         pass
...     def g(self, value):
...         pass
...
{'a': 1, '__module__': '__main__', 'd': {1: 'Robert', 2: 'Python'}, 'g': <function g at 0x7fe2482a1230>, 'f': <function f at 0x7fe248297668>}

  

能够看到,其返回的确实是class A的动态元信息

在前面,Python虚拟机已经得到了关于class的属性表(动态元信息),那么在build_class中,这个动态元信息将做为methods出如今build_class函数的参数列表中。有一点值的注意的是,methods中并无包含全部关于class的元信息,在methods中,只包含了在class中包含的属性和方法。从广义上来说,方法也是一种属性,因此咱们能够说,class的动态元信息中包含了class的全部属性

static PyObject *
build_class(PyObject *methods, PyObject *bases, PyObject *name)
{
    PyObject *metaclass = NULL, *result, *base;
    //[1]:检查属性表中是否有指定的__metaclass__
    if (PyDict_Check(methods))
        metaclass = PyDict_GetItemString(methods, "__metaclass__");
    if (metaclass != NULL)
        Py_INCREF(metaclass);
    else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
        //[2]:得到A的第一基类,object
        base = PyTuple_GET_ITEM(bases, 0);
        //[3]:得到object.__class__
        metaclass = PyObject_GetAttrString(base, "__class__");
        if (metaclass == NULL) {
            PyErr_Clear();
            metaclass = (PyObject *)base->ob_type;
            Py_INCREF(metaclass);
        }
    }
    else {
        ……
    }
    result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);
    ……
    return result;
}

  

虽然咱们虽然知道class的属性,但对于这个class对象的类型是什么,应该如何建立,要分配多少内存,却没有任何信息。在build_class中,metaclass正是关于class对象的另外一部分元信息,咱们称为静态元信息。在静态元信息中,隐藏着全部的class对象应该如何建立的信息,注意,咱们这里说的是全部的class对象

在build_class中,包含了为classic class和new style class肯定metaclass的过程,固然,这里咱们只考虑new style class肯定metaclass的过程

若是用户没有指定,Python虚拟机会选择class的第一基类的type做为该class的metaclass。对于这里的A来讲,其第一基类为object,而咱们已经知道object.__class__为<type 'type'>。因此最终得到的metaclass为<type 'type'>这个class对象

对于PyIntObject、PyDictObject这些对象,其全部的元信息都包含在其对应的类型对象中。而为何关于一个class对象的全部元信息不能包含在其自身当中,却要分离为两部分呢?由于用户会在源文件中定义不一样的class,其所包含的属性确定是不一样的,这就决定了只能使用动态机制来保存class的属性,这个元信息只能是动态的,因此咱们称为动态元信息,即咱们看到的参数methods,而对于全部的class均可能共用的元信息,好比class对象的type和class对象的建立策略,这些则存放在了class对象的metaclasss中

PyIntObject、PyDictObject这些对象是Python静态提供的,它们都具备相同的接口集合,固然,有的对象多是不支持某个接口,但不影响它的全部元信息能够彻底存储在其类型对象中:而用户自定义的class对象,其接口集合是动态的,不可能在metaclass中静态指定,如图1-3展现了多个class对象和元信息的关系

图1-4   class对象与元信息之间的关系

若是对动态元信息和静态元信息还有不理解的同窗能够这样想:如今咱们有教师类Teacher和厨师类Chief这两个类,教师拥有教书育人的能力,厨师拥有烹饪佳肴的能力,这是两个类的动态元信息,两个类的能力不一样,但相同的是教师和厨师都是人,都是两个眼睛一张嘴,那么这些公共的信息就是静态元信息,由于教师和厨师都是人,若是咱们有创造人的能力,必定是基于两个眼睛一张嘴的限定来创造,否则就是别的物种了

调用metaclass完成class对象的建立

在了解class对象的建立以前,咱们再回顾一下PyType_Type

typeobject.c

PyTypeObject PyType_Type = {
	PyObject_HEAD_INIT(&PyType_Type)
	……
	(ternaryfunc)type_call,			/* tp_call */
	……
	type_new,				/* tp_new */
	……
};

  

tp_call、tp_new在建立class对象中,起着相当重要的做用,同时PyObject_HEAD_INIT(&PyType_Type)这行代码表明,PyType_Type->ob_type指向的是其自身

如今,咱们开始解析如何建立class对象。在得到了metaclass以后,build_class经过PyObject_CallFunctionObjArgs函数完成“调用metaclass”的动做,从而完成class对象的建立。以前说过,Python中一个对象是否可调用,要看其是否认义了tp_call,当调用一个对象时,会将对象传入PyObject_Call函数,这个函数中会调用其对象的tp_call,从而完成对象的调用。很幸运,PyType_Type定义了tp_call,这说明PyType_Type是一个可调用的对象

如今问题来了,一个Python程序中class对象可能成千上万,而PyType_Type却只有一个,这一个PyType_Type如何建立出不一样的class对象呢?其中的奥妙则集中以前咱们所看到的PyObject_CallFunctionObjArgs函数的几个参数中,这几个参数分别是class的类名、基类列表和属性表,在PyObject_CallFunctionObjArgs中,这几个参数会被打包到一个tupple对象中,最终进入PyObject_Call函数,如今,让咱们进入到建立class对象处:

//object.h
typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *);
 
//abstract.c
PyObject * PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
{
    //arg便是PyObject_CallFunctionObjArgs中打包的tupple对象
    ternaryfunc call;
 
    if ((call = func->ob_type->tp_call) != NULL) {
        PyObject *result = (*call)(func, arg, kw);
        if (result == NULL && !PyErr_Occurred())
            PyErr_SetString(
                PyExc_SystemError,
                "NULL result without error in PyObject_Call");
        return result;
    }
    PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
             func->ob_type->tp_name);
    return NULL;
}

  

最终,因为PyType_Type的ob_type仍是指向PyType_Type,因此最终将调用到PyType_Type中定义的tp_call操做。下面,来看一下PyType_Type的tp_call操做:

static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj;
 
    if (type->tp_new == NULL) {
        PyErr_Format(PyExc_TypeError,
                 "cannot create '%.100s' instances",
                 type->tp_name);
        return NULL;
    }
 
    obj = type->tp_new(type, args, kwds);
    //若是建立的是实例对象,则调用__init__进行初始化
    if (obj != NULL) {
        if (type == &PyType_Type &&
            PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
            (kwds == NULL ||
             (PyDict_Check(kwds) && PyDict_Size(kwds) == 0)))
            return obj;
        if (!PyType_IsSubtype(obj->ob_type, type))
            return obj;
        type = obj->ob_type;
        if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_CLASS) &&
            type->tp_init != NULL &&
            type->tp_init(obj, args, kwds) < 0) {
            Py_DECREF(obj);
            obj = NULL;
        }
    }
    return obj;
}

  

PyType_Type中的tp_new指向tp_new,而这个tp_new才是class对象建立的地方

typeobject.c

static PyObject * type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
    //metatype是PyType_Type(<type 'type'>),args中包含了(类名、基类列表、属性表)
    PyObject *name, *bases, *dict;
    static char *kwlist[] = {"name", "bases", "dict", 0};
    PyObject *slots, *tmp, *newslots;
    PyTypeObject *type, *base, *tmptype, *winner;
    PyHeapTypeObject *et;
    PyMemberDef *mp;
    Py_ssize_t i, nbases, nslots, slotoffset, add_dict, add_weak;
    int j, may_add_dict, may_add_weak;
 
    ……
    //将args中的(类名、基类列表、属性表)分别解析到name、bases、dict三个变量中
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "SO!O!:type", kwlist,
                     &name,
                     &PyTuple_Type, &bases,
                     &PyDict_Type, &dict))
        return NULL;
	……
    //肯定最佳metaclass,存储在PyObject *metatype中
	winner = metatype;
	for (i = 0; i < nbases; i++) {
		tmp = PyTuple_GET_ITEM(bases, i);
		tmptype = tmp->ob_type;
		if (tmptype == &PyClass_Type)
			continue; /* Special case classic classes */
		if (PyType_IsSubtype(winner, tmptype))
			continue;
		if (PyType_IsSubtype(tmptype, winner)) {
			winner = tmptype;
			continue;
		}
		PyErr_SetString(PyExc_TypeError,
				"metaclass conflict: "
				"the metaclass of a derived class "
				"must be a (non-strict) subclass "
				"of the metaclasses of all its bases");
		return NULL;
	}
	if (winner != metatype) {
		if (winner->tp_new != type_new) /* Pass it to the winner */
			return winner->tp_new(winner, args, kwds);
		metatype = winner;
	}
	……
    //肯定最佳base,存储在PyObject *base中
	base = best_base(bases);
	……
    //为class对象申请内存,尽管PyType_Type的tp_alloc为0,但PyBaseObject_Type的tp_alloc为PyType_GenericAlloc,在PyType_Ready中被继承了,建立的内存大小为tp_basicsize + tp_itemsize
    type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);
    et = (PyHeapTypeObject *)type;
    et->ht_name = name;
    //设置PyTypeObject中的各个域
    type->tp_as_number = &et->as_number;
    type->tp_as_sequence = &et->as_sequence;
    type->tp_as_mapping = &et->as_mapping;
    type->tp_as_buffer = &et->as_buffer;
    type->tp_name = PyString_AS_STRING(name);
    //设置基类和基类列表
    type->tp_bases = bases;
    type->tp_base = base;
 
    //设置属性列表
    type->tp_dict = dict = PyDict_Copy(dict);
    //若是自定义
    tmp = PyDict_GetItemString(dict, "__new__");
    if (tmp != NULL && PyFunction_Check(tmp)) {
        tmp = PyStaticMethod_New(tmp);
        if (tmp == NULL) {
            Py_DECREF(type);
            return NULL;
        }
        PyDict_SetItemString(dict, "__new__", tmp);
        Py_DECREF(tmp);
    }
 
    //[1]:为class对象对应的instance对象设置内存大小信息
    slotoffset = base->tp_basicsize;
    if (add_dict) {
        if (base->tp_itemsize)
            type->tp_dictoffset = -(long)sizeof(PyObject *);
        else
            type->tp_dictoffset = slotoffset;
        slotoffset += sizeof(PyObject *);
    }
    if (add_weak) {
        assert(!base->tp_itemsize);
        type->tp_weaklistoffset = slotoffset;
        slotoffset += sizeof(PyObject *);
    }
    type->tp_basicsize = slotoffset;
    type->tp_itemsize = base->tp_itemsize;
    type->tp_members = PyHeapType_GET_MEMBERS(et);
    //调用PyType_Ready对class对象进行初始化
    if (PyType_Ready(type) < 0) {
        Py_DECREF(type);
        return NULL;
    }
 
    return (PyObject *)type;
}

  

Python虚拟机首先会将类名、基类列表和属性表从args这个tupple对象中解析出来,而后会基于基类列表及传入的metaclass(参数metatype)肯定最佳的metaclass和base,对于咱们的A来讲,最佳的metaclass为<type 'type'>,最佳的base为<type 'object'>

随后,Python虚拟机会调用metatype->tp_alloc尝试为所要建立的与A对应的class对象分配内存,这里须要注意的是,在PyType_Type中,咱们会发现tp_alloc为NULL,那这样一调用Python虚拟机还不当即报错?别忘了,在Python进行初始化时,有一项动做就是从基类继承各类操做,因为type.__bases__中的第一基类是<type 'object'>,因此<type 'type'>会继承<type 'object'>的tp_alloc操做,即PyType_GenericAlloc。对于咱们的A(或者说,对于任何继承自object的class对象来讲)PyType_GenericAlloc最终将申请metatype->tp_basicsize+metatype->tp_itemsize大小的空间。从PyType_Type的定义中咱们能够看到,这个大小实际上就是sizeof(PyHeapTypeObject)+sizeof(PyMemberDef)。到这里就能明白为何会有PyHeapTyoeObject,原来是为了用户自定义class对象准备的

此后,就是设置<class A>这个class对象的各个域,其中包括了在tp_dict上设置了属性表,在上述代码[1]处,这里计算了与<class A>对应的instance对象的内存大小信息,换句话说,之后经过a = A()这样的表达式建立一个instance对象时,须要为这个instance对象申请多大的内存呢?对于A(任何继承自object的class对象也成立)来讲,这个大小为PyBaseObject_Type->tp_basicsize+8。其中的8为2*sizeof(PyObject *)。为何后面要跟着两个PyObject *的空间,并且这些空间的地址被设置给tp_dictoffset和tp_weaklistoffset呢?这个之后还会解释

最后,Python虚拟机还会调用PyType_Ready对<class A>进行和内置class对象同样的初始化动做。到此,A对应的class对象正式建立完毕。图1-5显示了用户自定义class对象和内置class对象最终在布局上的区别

图1-5   用户自定义class对象和内置class对象的内存布局对比

本质上,不管是用户自定义的class对象仍是内置的class对象,在Python虚拟机内部,均可以用一个PyTypeObject来表示。但不一样的是,内置class对象的PyTypeObject及其关联的PyNumberMethods等内存位置都是在编译时肯定的,它们在内存中的位置是分离的,而用户自定义的class对象的PyTypeObject和PyNumberMethods等的内存位置是连续的,必须在运行时动态申请内存

如今,咱们对Python中“可调用”这个概念应该有了必定的认识,在Python中,不拘对象、不拘大小,只要对象定义了tp_call操做,就能进行调用操做。咱们已经看到,Python中的class对象是调用metaclass对象建立的。若是按照这个逻辑往前推测,那么调用class对象,是否是就能获得instance对象的,在后面分析instance对象的建立,还会介绍

相关文章
相关标签/搜索