Python虚拟机类机制之descriptor(三)
從slot到descriptor
在Python虛擬機(jī)類(lèi)機(jī)制之填充tp_dict(二)這一章的末尾,我們介紹了slot,slot包含了很多關(guān)于一個(gè)操作的信息,但是很可惜,在tp_dict中,與__getitem__關(guān)聯(lián)在一起的,一定不會(huì)是一個(gè)slot,原因很簡(jiǎn)單,slot不是一個(gè)PyObject,它不能存放在dict對(duì)象中。當(dāng)然,我們?cè)偕钊胨伎家幌?#xff0c;會(huì)發(fā)現(xiàn)slot也不會(huì)被“調(diào)用”。既然slot不是一個(gè)PyObject,那么它就沒(méi)有type,也就無(wú)從談起什么tp_call了,所以slot是無(wú)論如何也不滿(mǎn)足前面所描述的Python的“可調(diào)用”這個(gè)概念
前面我們說(shuō)過(guò),Python虛擬機(jī)會(huì)在tp_dict找到__geiitem__對(duì)應(yīng)的操作后,調(diào)用該操作,所以在tp_dict中與__getitem__對(duì)應(yīng)的只能是另一個(gè)包裝了slot的PyObject,在Python中,我們稱(chēng)為descriptor
在Python內(nèi)部,存在多種descriptor,與PyTypeObject中的操作對(duì)應(yīng)的是PyWrapperDescrObject。在此后的描述,我們將用術(shù)語(yǔ)descriptor來(lái)專(zhuān)門(mén)表示PyWrapperDescrObject。一個(gè)descriptor包含一個(gè)slot,其創(chuàng)建是通過(guò)PyDescr_NewWrapper
descrobject.h?
#define PyDescr_COMMON \PyObject_HEAD \PyTypeObject *d_type; \PyObject *d_nametypedef struct {PyDescr_COMMON;struct wrapperbase *d_base;void *d_wrapped; /* This can be any function pointer */ } PyWrapperDescrObject;
descrobject.c
static PyDescrObject * descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name) {PyDescrObject *descr;//申請(qǐng)空間descr = (PyDescrObject *)PyType_GenericAlloc(descrtype, 0);if (descr != NULL) {Py_XINCREF(type);descr->d_type = type;descr->d_name = PyString_InternFromString(name);if (descr->d_name == NULL) {Py_DECREF(descr);descr = NULL;}}return descr; }PyObject * PyDescr_NewWrapper(PyTypeObject *type, struct wrapperbase *base, void *wrapped) {PyWrapperDescrObject *descr;descr = (PyWrapperDescrObject *)descr_new(&PyWrapperDescr_Type,type, base->name);if (descr != NULL) {descr->d_base = base;descr->d_wrapped = wrapped;}return (PyObject *)descr; }
Python內(nèi)部的各種descriptor都將包含PyDescr_COMMON,其中的d_type被設(shè)置為PyDescr_NewWrapper的參數(shù)type,而d_wrapped則存放著最重要的信息:操作對(duì)應(yīng)的函數(shù)指針,比如對(duì)于PyList_Type來(lái)說(shuō),其tp_dict["__getitem__"].d_wrapped就是&mp_subscript。而slot則被存放在了d_base中
PyWrapperDescrObject的type是PyWrapperDescr_Type,其中的tp_call是wrapperdescr_call,當(dāng)Python虛擬機(jī)調(diào)用一個(gè)descriptor時(shí),也就會(huì)調(diào)用wrapperdescr_call,對(duì)于descriptor的調(diào)用過(guò)程,后面還會(huì)詳細(xì)解析
建立聯(lián)系
排序后的結(jié)果仍然存放在slotdefs中,Python虛擬機(jī)就可以從頭到尾遍歷slotdefs,基于每一個(gè)slot建立一個(gè)descriptor,然后在tp_dict中建立從操作名到descriptor的關(guān)聯(lián),這個(gè)過(guò)程在add_operators中完成
typeobject.c
static int add_operators(PyTypeObject *type) {PyObject *dict = type->tp_dict;slotdef *p;PyObject *descr;void **ptr;//對(duì)slotdefs進(jìn)行排序init_slotdefs();for (p = slotdefs; p->name; p++) {//如果slot中沒(méi)有指定wrapper,則不處理if (p->wrapper == NULL)continue;//獲得slot對(duì)應(yīng)的操作在PyTypeObject中的函數(shù)指針ptr = slotptr(type, p->offset);if (!ptr || !*ptr)continue;//如果tp_dict中存在操作名,則放棄if (PyDict_GetItem(dict, p->name_strobj))continue;//創(chuàng)建descriptordescr = PyDescr_NewWrapper(type, p, *ptr);if (descr == NULL)return -1;//將(操作名,descriptor)放入tp_dict中if (PyDict_SetItem(dict, p->name_strobj, descr) < 0)return -1;Py_DECREF(descr);}if (type->tp_new != NULL) {if (add_tp_new_wrapper(type) < 0)return -1;}return 0; }
在add_operators中,首先會(huì)調(diào)用前面剖析過(guò)的init_slotdefs函數(shù)進(jìn)行排序,然后遍歷排序完后的slotdefs結(jié)構(gòu)體數(shù)組,對(duì)其中每一個(gè)slot(slotdef),通過(guò)slotptr獲得該slot對(duì)應(yīng)的操作在PyTypeObject中的函數(shù)指針,并接著創(chuàng)建descriptor,在tp_dict中建立從操作名(slotdef.name_strobj)到操作(descriptor)的關(guān)聯(lián)
需要注意的是,在創(chuàng)建descriptor之前,Python虛擬機(jī)會(huì)檢查在tp_dict中操作名是否已存在,如果已經(jīng)存在,則不會(huì)再次建立從操作名到操作的關(guān)聯(lián)。正是這種檢查機(jī)制與上面的排序機(jī)制相結(jié)合,使得Python虛擬機(jī)能夠在擁有相同操作名的多個(gè)操作中選擇優(yōu)先級(jí)最高的操作
在add_operators中,上面描述的動(dòng)作都很直觀(guān)、簡(jiǎn)單。而最難的動(dòng)作隱藏在slotptr這個(gè)函數(shù)中,它的功能是完成slot到slot對(duì)應(yīng)操作的真實(shí)函數(shù)指針的轉(zhuǎn)換。我們已經(jīng)知道,在slot中存放著操作的offset,但很不幸,這個(gè)offset是相對(duì)于PyHeadTypeObject的偏移,而操作的真實(shí)函數(shù)指針則在PyTypeObject中指定。更不幸的是,PyTypeObject和PyHeadTypeObject不是同構(gòu)的,因?yàn)镻yHeadTypeObject中包含了PyNumberMethods結(jié)構(gòu)體,而PyTypeObject中只包含了PyNumberMethods*指針。所以slot中存儲(chǔ)的這個(gè)關(guān)于操作的offset對(duì)于PyTypeObject來(lái)說(shuō),不可能直接使用,必須通過(guò)轉(zhuǎn)換
舉個(gè)例子,假如說(shuō)調(diào)用slotptr(&PyList_Type, offset(PyHeadTypeObject, mp_subscript)),首先判斷這個(gè)偏移大于offset(PyHeadTypeObject, as_mapping),所以會(huì)先從PyTypeObject對(duì)象中獲得as_mapping指針P,然后在P的基礎(chǔ)上進(jìn)行偏移就可以得到實(shí)際的函數(shù)地址了,而偏移量delta為:
delta = offset(PyHeadTypeObject, mp_subscript) - offset(PyHeadTypeObject, as_mapping)
這個(gè)復(fù)雜的轉(zhuǎn)換過(guò)程在slotptr中完成:
typeobject.c
static void ** slotptr(PyTypeObject *type, int ioffset) {char *ptr;long offset = ioffset;assert(offset >= 0);assert((size_t)offset < offsetof(PyHeapTypeObject, as_buffer));//判斷從PyHeapTypeObject中排后面的PySequenceMethods開(kāi)始if ((size_t)offset >= offsetof(PyHeapTypeObject, as_sequence)) {ptr = (char *)type->tp_as_sequence;offset -= offsetof(PyHeapTypeObject, as_sequence);}else if ((size_t)offset >= offsetof(PyHeapTypeObject, as_mapping)) {ptr = (char *)type->tp_as_mapping;offset -= offsetof(PyHeapTypeObject, as_mapping);}else if ((size_t)offset >= offsetof(PyHeapTypeObject, as_number)) {ptr = (char *)type->tp_as_number;offset -= offsetof(PyHeapTypeObject, as_number);}else {ptr = (char *)type;}if (ptr != NULL)ptr += offset;return (void **)ptr; }
為什么判斷首先從PySequenceMethods開(kāi)始,然后向前,依次判斷PyMappingMethods和PyNumberMethods呢?假如我們先從PyNumberMethods開(kāi)始判斷,如果一個(gè)操作的offset大于PyHeadTypeObject中as_number在PyNumberMethods的偏移量,那么我們還是沒(méi)有辦法確定在這個(gè)操作是屬于PyNumberMethods還是屬于PyMappingMethods或PySequenceMethods。只有從后往前進(jìn)行判斷,才能解決這個(gè)問(wèn)題
現(xiàn)在,我們摸清楚Python在改造PyTypeObject時(shí)對(duì)tp_dict做了什么,圖1-1顯示了PyList_Type完成初始化之后的整個(gè)布局,其中包括我們討論的descriptor和slot
圖1-1? ?add_operators完成之后的PyList_Type
在圖1-1中,PyList_Type.tp_as_mapping中延伸出去的部分是在編譯時(shí)已經(jīng)確定好的,而從tp_dict中延伸出去的部分是在Python運(yùn)行時(shí)環(huán)境初始化才建立的。
PyType_Ready在通過(guò)add_operators添加了PyTypeObject對(duì)象中定義了的一些操作后,還會(huì)通過(guò)add_methods、add_members、add_getset添加在PyTypeObject中定義的tp_methods、tp_members和tp_getset函數(shù)集,這些過(guò)程與add_operators類(lèi)似,不過(guò)最后添加到tp_dict中的descriptor就不再是PyWrapperDescrObject,而分別是PyMethodDescrObject、PyMemberDescrObject、PyGetSetDescrObject
圖1-1所顯示的class對(duì)象大部分正確,但還不算全部正確,考慮下面的例子:?
>>> class A(list): ... def __repr__(self): ... return "Python" ... >>> s = "%s" % A() >>> s 'Python'
熟悉Python的人都知道,__repr__是Python中的特殊方法。當(dāng)Python執(zhí)行表達(dá)式"s = '%s' %A()"時(shí),最終會(huì)調(diào)用A.tp_repr。如果按照?qǐng)D1-1的布局,并且對(duì)照PyList_Type,那么就應(yīng)該調(diào)用list_repr這個(gè)函數(shù),但并不是這樣的,Python虛擬機(jī)最終調(diào)用的是A中重寫(xiě)后的__repr__。這意味著,Python在初始化A時(shí),對(duì)tp_repr進(jìn)行了特殊處理。為什么Python虛擬機(jī)會(huì)知道要對(duì)tp_repr進(jìn)行特殊處理呢?答案還是在slot身上
在slotdefs中,有一條slot為T(mén)PSLOT:
typeobject.c
TPSLOT("__repr__", tp_repr, slot_tp_repr, wrap_unaryfunc, "x.__repr__() <==> repr(x)")
Python虛擬機(jī)在初始化A時(shí),會(huì)檢查<class A>的tp_dict中是否存在__repr__。在后面剖析用戶(hù)自定義的class對(duì)象時(shí),我們會(huì)看到,因?yàn)樵诙xclass A時(shí)重寫(xiě)__repr__這個(gè)操作,所以A.tp_dict中__repr__一開(kāi)始就會(huì)存在,Python虛擬機(jī)會(huì)檢測(cè)到它的存在。一旦檢測(cè)到__repr__存在,Python虛擬機(jī)將tp_repr這個(gè)函數(shù)指針替換為slot中指定的&slot_tp_repr。所以當(dāng)Python虛擬機(jī)調(diào)用A.tp_repr時(shí),實(shí)際上執(zhí)行的是slot_tp_repr
typeobject.c
static PyObject * slot_tp_repr(PyObject *self) {PyObject *func, *res;static PyObject *repr_str;//[1]:查找__repr__屬性func = lookup_method(self, "__repr__", &repr_str);if (func != NULL) {//[2]:調(diào)用__repr__對(duì)應(yīng)的對(duì)象res = PyEval_CallObject(func, NULL);Py_DECREF(func);return res;}PyErr_Clear();return PyString_FromFormat("<%s object at %p>",self->ob_type->tp_name, self); }
在slot_tp_repr中,會(huì)尋找__repr__屬性對(duì)應(yīng)的對(duì)象,正好就會(huì)找到我們?cè)贏中重寫(xiě)的函數(shù),這個(gè)對(duì)象其實(shí)是一個(gè)PyFunctionObject。這樣一來(lái),就完成了對(duì)默認(rèn)list的repr行為的替換,所以對(duì)A來(lái)說(shuō),其初始化結(jié)束后的內(nèi)存布局則如圖1-2所示:
圖1-2? ?初始化完成后的A
?
當(dāng)然,并是不會(huì)A中所有的操作都會(huì)有這樣的變化。A的其他操作還是會(huì)指向PyList_Type中指定的函數(shù),比如tp_iter還是會(huì)指向list_iter。對(duì)于A來(lái)說(shuō),這個(gè)變化是在fixup_slot_dispatchers(PyTypeObject* type)中完成的,對(duì)于內(nèi)置class對(duì)象,不會(huì)進(jìn)行這樣的操作,這個(gè)操作是屬于創(chuàng)建自定義class對(duì)象時(shí)的動(dòng)作
對(duì)于A來(lái)說(shuō),這個(gè)變化是在fixup_slot_dispatchers(PyTypeObject* type)中完成的,對(duì)于內(nèi)置class對(duì)象,不會(huì)進(jìn)行這樣的操作,這個(gè)操作是屬于創(chuàng)建自定義class對(duì)象時(shí)的動(dòng)作
確定MRO
所謂的MRO,即是指Method Resolve Order,更一般地,也是一個(gè)class對(duì)象的屬性解析順序。如果Python像java那樣僅支持單繼承,那就不是一個(gè)問(wèn)題了。但是Python是支持多繼承的,在多重繼承時(shí),就必須設(shè)置按照何種順序解析屬性,考慮如下Python代碼:
>>> class A(list): ... def show(self): ... print("A::show") ... >>> class B(list): ... def show(self): ... print("B::show") ... >>> class C(A): ... pass ... >>> class D(C, B): ... pass ... >>> d = D() >>> d.show() A::show
由于D的基類(lèi)A和B中都實(shí)現(xiàn)了show,那么在調(diào)用d.show()時(shí),究竟是調(diào)用A的show方法還是B的show方法呢?Python內(nèi)部在PyType_Ready中通過(guò)mro_internal函數(shù)完成了對(duì)一個(gè)類(lèi)型的mro順序的建立。Python虛擬機(jī)將創(chuàng)建一個(gè)tupple對(duì)象,在對(duì)象中依次存放著一組class對(duì)象。在tupple中,class對(duì)象的順序就是Python虛擬機(jī)在解析屬性時(shí)的mro順序。最終這個(gè)tupple將被保存在PyTypeObject.tp_mro中
對(duì)于上述的class D,Python虛擬機(jī)會(huì)在內(nèi)部創(chuàng)建一個(gè)list,其中根據(jù)D的聲明依次放入D和它的基類(lèi),如圖1-3所示:
圖1-3? ?D建立mro列表時(shí)Python虛擬機(jī)內(nèi)部的輔助list
?注意在list的最后一項(xiàng)存放著一個(gè)包含所有D的直接基類(lèi)列表。Python虛擬機(jī)將從左到右遍歷該list,當(dāng)訪(fǎng)問(wèn)到list中的任一個(gè)基類(lèi)時(shí),如果基類(lèi)存在mro列表,則會(huì)轉(zhuǎn)而訪(fǎng)問(wèn)基類(lèi)的mro列表。在訪(fǎng)問(wèn)的過(guò)程中,不斷將所訪(fǎng)問(wèn)到的class對(duì)象放入到D自身的mro列表中
我們跟蹤這個(gè)遍歷的過(guò)程來(lái)看一下:
當(dāng)遍歷的過(guò)程結(jié)束后,D的mro列表也就存儲(chǔ)了一個(gè)class對(duì)象的順序列表了。從上面的遍歷過(guò)程可以看到,這個(gè)列表是(D、C、A、B、list、object),我們可以來(lái)驗(yàn)證一下:
>>> for t in D.__mro__: ... print(t) ... <class '__main__.D'> <class '__main__.C'> <class '__main__.A'> <class '__main__.B'> <class 'list'> <class 'object'>
圖1-4? ?展示不同順序下mro列表
繼承基類(lèi)操作
Python虛擬機(jī)確定了mro列表后,就會(huì)遍歷mro列表(注意,由于第一個(gè)class對(duì)象的mro列表的第一項(xiàng)總是其自身,所以遍歷是從第二項(xiàng)開(kāi)始的)。在mro列表中實(shí)際上還存儲(chǔ)的就是class對(duì)象的所有直接和間接基類(lèi),Python虛擬機(jī)會(huì)將class對(duì)象自身沒(méi)有設(shè)置而基類(lèi)中設(shè)置了的操作拷貝到class對(duì)象中,從而完成對(duì)基類(lèi)操作的繼承動(dòng)作:
這個(gè)繼承操作的動(dòng)作發(fā)生在inherit_slots中
typeobject.c
int PyType_Ready(PyTypeObject *type) {……bases = type->tp_mro;n = PyTuple_GET_SIZE(bases);for (i = 1; i < n; i++) {PyObject *b = PyTuple_GET_ITEM(bases, i);if (PyType_Check(b))inherit_slots(type, (PyTypeObject *)b);}…… }
在inherit_slots中,會(huì)拷貝相當(dāng)多的操作,這里我們拿nb_add來(lái)做個(gè)例子:
typeobject.c
static void inherit_slots(PyTypeObject *type, PyTypeObject *base) {PyTypeObject *basebase;#define SLOTDEFINED(SLOT) \(base->SLOT != 0 && \(basebase == NULL || base->SLOT != basebase->SLOT))#define COPYSLOT(SLOT) \if (!type->SLOT && SLOTDEFINED(SLOT)) type->SLOT = base->SLOT#define COPYNUM(SLOT) COPYSLOT(tp_as_number->SLOT)if (type->tp_as_number != NULL && base->tp_as_number != NULL) {basebase = base->tp_base;if (basebase->tp_as_number == NULL)basebase = NULL;COPYNUM(nb_add);……}…… }
我們知道PyBool_Type中并沒(méi)有設(shè)置nb_add操作,但它的tp_base設(shè)置的是&PyInt_Type,而PyInt_Type中卻設(shè)置了nb_add操作。所以我們可以在PyType_Ready中添加輸出語(yǔ)句,當(dāng)處理type分別為bool和int時(shí),輸出其nb_add的地址,進(jìn)行驗(yàn)證。因?yàn)榘凑読nherit_slots的結(jié)果,這兩個(gè)地址應(yīng)該都指向同一個(gè)地址,即int_add的地址
typeobject.c
int PyType_Ready(PyTypeObject *type) {……for (i = 1; i < n; i++) {PyObject *b = PyTuple_GET_ITEM(bases, i);if (PyType_Check(b))inherit_slots(type, (PyTypeObject *)b);}//打印bool中的nb_add地址if (strcmp(type->tp_name, "bool") == 0) {printf("bool nb_add: 0x%X\n", *(type->tp_as_number->nb_add));}//打印int中的nb_add地址if (strcmp(type->tp_name, "int") == 0) {printf("int nb_add: 0x%X\n", *(type->tp_as_number->nb_add));}…… }
然后打開(kāi)Python命令行,可以看到int類(lèi)型和bool類(lèi)型在初始化時(shí)打印其nb_add地址:
# ./python int nb_add: 0x43C570 bool nb_add: 0x43C570
這個(gè)結(jié)果預(yù)示著Python中的兩個(gè)bool對(duì)象,我們可以進(jìn)行加法操作
填充基類(lèi)中的子類(lèi)列表
到這里,PyType_Ready還剩下最后一個(gè)重要的動(dòng)作了:設(shè)置基類(lèi)中的子類(lèi)列表。在每一個(gè)PyTypeObject中,有一個(gè)tp_subclasses,這個(gè)東西在PyType_Type完成后將是一個(gè)list對(duì)象。其中存放著所有直接繼承該類(lèi)型的class對(duì)象。PyType_Ready通過(guò)調(diào)用add_subclass完成這個(gè)向tp_subclass中填充子類(lèi)對(duì)象的動(dòng)作
typeobject.c
int PyType_Ready(PyTypeObject *type) {PyObject *dict, *bases;PyTypeObject *base;Py_ssize_t i, n;……bases = type->tp_bases;……n = PyTuple_GET_SIZE(bases);for (i = 0; i < n; i++) {PyObject *b = PyTuple_GET_ITEM(bases, i);if (PyType_Check(b) &&add_subclass((PyTypeObject *)b, type) < 0)goto error;}…… }
我們驗(yàn)證這個(gè)子類(lèi)列表的存在:?
>>> int.__subclasses__() [<class 'bool'>] >>> object.__subclasses__() [<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>,…… >>>
果然,object是萬(wàn)物之母,很多的類(lèi)都直接繼承于object。可以看到,Python虛擬機(jī)對(duì)Python的內(nèi)置類(lèi)型對(duì)應(yīng)的PyTypeObject進(jìn)行了多種復(fù)雜的改造工作,總結(jié)一下,主要包括:
- 設(shè)置type信息,基類(lèi)及基類(lèi)列表
- 填充tp_dict
- 確定mro列表
- 基于mro列表從基類(lèi)繼承操作
- 設(shè)置基類(lèi)的子類(lèi)列表
?
轉(zhuǎn)載于:https://www.cnblogs.com/beiluowuzheng/p/9621918.html
總結(jié)
以上是生活随笔為你收集整理的Python虚拟机类机制之descriptor(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [SCOI2007] 蜥蜴 (最大流)
- 下一篇: 【Python】多种方式实现生成验证码