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對象的建立,還會介紹

相關文章
相關標籤/搜索