python虛擬機(jī)之描述器實(shí)現(xiàn)原理與源碼分析
從字節(jié)碼角度看描述器
在前面的內(nèi)容當(dāng)中我們已經(jīng)詳細(xì)分析了描述器的使用和其相關(guān)的應(yīng)用,我們通常使用描述器都是將其作為類的一個(gè)類屬性使用,而使用的方式就是 a.attr
,而這個(gè)使用方式使用的字節(jié)碼如下所示:
Python 3.10.9 (main, Jan 11 2023, 09:18:18) [Clang 14.0.6 ] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import dis >>> dis.dis("a.attr") 1 0 LOAD_NAME 0 (a) 2 LOAD_ATTR 1 (attr) 4 RETURN_VALUE >>>
可以看到的是真正調(diào)用的字節(jié)碼是 LOAD_ATTR
,因此只需要我們深入 LOAD_ATTR
指令我們就能夠了解這其中所有發(fā)生的內(nèi)容,了解魔法背后的神秘。
描述器源碼分析
cpython 虛擬機(jī)當(dāng)中執(zhí)行這個(gè)字節(jié)碼的內(nèi)容如下:
TARGET(LOAD_ATTR) { PyObject *name = GETITEM(names, oparg); PyObject *owner = TOP(); PyObject *res = PyObject_GetAttr(owner, name); Py_DECREF(owner); SET_TOP(res); if (res == NULL) goto error; DISPATCH(); }
owner
對(duì)應(yīng)上面的代碼當(dāng)中的 a
對(duì)象,name
對(duì)應(yīng)上面的字符串 attr
。從上面的代碼分析我們可以知道真正獲取屬性的函數(shù)為 PyObject_GetAttr
,這個(gè)函數(shù)的源程序如下所示:
PyObject * PyObject_GetAttr(PyObject *v, PyObject *name) { // 首先獲取對(duì)象 v 的類型 ,對(duì)應(yīng)上面的代碼的話就是找到對(duì)象 a 的類型 PyTypeObject *tp = Py_TYPE(v); if (!PyUnicode_Check(name)) { PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", name->ob_type->tp_name); return NULL; } // 獲取對(duì)象的 tp_getattro 函數(shù) 這個(gè)函數(shù)就是負(fù)責(zé)屬性查找的函數(shù) 我們一般使用的這個(gè)屬性查找函數(shù)都是 // object 這個(gè)基類的屬性查找函數(shù) if (tp->tp_getattro != NULL) return (*tp->tp_getattro)(v, name); if (tp->tp_getattr != NULL) { const char *name_str = PyUnicode_AsUTF8(name); if (name_str == NULL) return NULL; return (*tp->tp_getattr)(v, (char *)name_str); } PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); return NULL; }
在上面的代碼當(dāng)中我們提到了 object 這個(gè)基類,因?yàn)槲覀冃枰业剿膶傩圆檎液瘮?shù),因此我們看一下這個(gè)基類在 cpython 內(nèi)部的定義,在 cpython 內(nèi)部 object 基類定義為 PyBaseObject_Type
:
PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "object", /* tp_name */ sizeof(PyObject), /* tp_basicsize */ 0, /* tp_itemsize */ object_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ object_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)_Py_HashPointer, /* tp_hash */ 0, /* tp_call */ object_str, /* tp_str */ // 這個(gè)就是真正的屬性查找函數(shù) PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericSetAttr, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ PyDoc_STR("object()\n--\n\nThe most base type"), /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ object_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ object_methods, /* tp_methods */ 0, /* tp_members */ object_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ object_init, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ object_new, /* tp_new */ PyObject_Del, /* tp_free */ }; // 從上面的 object 定義可以看到真正的查找函數(shù)為 PyObject_GenericGetAttr 其函數(shù)內(nèi)容如下所示: PyObject * PyObject_GenericGetAttr(PyObject *obj, PyObject *name) { return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0); }
_PyObject_GenericGetAttrWithDict
函數(shù)定義如下所示:
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */ PyObject * _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict, int suppress) { /* Make sure the logic of _PyObject_GetMethod is in sync with this method. When suppress=1, this function suppress AttributeError. */ // 首先獲取對(duì)象的類型 針對(duì)于上面的源代碼來(lái)說(shuō)就是找到對(duì)象 a 的類型 PyTypeObject *tp = Py_TYPE(obj); PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; Py_ssize_t dictoffset; PyObject **dictptr; if (!PyUnicode_Check(name)){ PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", name->ob_type->tp_name); return NULL; } Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } // 這個(gè)是從所有的基類當(dāng)中找到一個(gè)名字為 name 的對(duì)象 如果沒(méi)有就返回 NULL // 這里的過(guò)程還是比較復(fù)雜 需要從類的 mro 序列當(dāng)中進(jìn)行查找 descr = _PyType_Lookup(tp, name); f = NULL; // 如果找到的類對(duì)象不為空 也就是在類本身或者基類當(dāng)中找到一個(gè)名為 name 的對(duì)象 if (descr != NULL) { Py_INCREF(descr); // 得到類對(duì)象的 __get__ 函數(shù) f = descr->ob_type->tp_descr_get; // 如果對(duì)象有 __get__ 函數(shù)則進(jìn)行進(jìn)一步判斷 if (f != NULL && PyDescr_IsData(descr)) { // PyDescr_IsData(descr) 這個(gè)宏是查看對(duì)象是否有 __set__ 函數(shù) // 如果是類對(duì)象又有 __get__ 函數(shù) 又有 __set__ 函數(shù) 則直接調(diào)用對(duì)象的 __get__ 函數(shù) 并且將結(jié)果返回 // 這里需要注意一下優(yōu)先級(jí) 這個(gè)優(yōu)先級(jí)是最高的 如果一個(gè)類對(duì)象定義了 __set__ 和 __get__ 函數(shù),那么 // 就會(huì)直接調(diào)用類對(duì)象的 __get__ 函數(shù)并且將這個(gè)函數(shù)的返回值返回 res = f(descr, obj, (PyObject *)obj->ob_type); if (res == NULL && suppress && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); } goto done; } } // 如果沒(méi)有名為 name 的類對(duì)象 或者雖然有名為 name 的對(duì)象 但是只要沒(méi)有同時(shí)定義 __get__ 和 __set__ 函數(shù)就需要 // 繼續(xù)往下執(zhí)行 從對(duì)象本省的 dict 當(dāng)中尋找 if (dict == NULL) { /* Inline _PyObject_GetDictPtr */ // 這部分代碼就是從對(duì)象 obj 當(dāng)中找到對(duì)象的 __dict__ 字段 dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { if (dictoffset < 0) { Py_ssize_t tsize; size_t size; tsize = ((PyVarObject *)obj)->ob_size; if (tsize < 0) tsize = -tsize; size = _PyObject_VAR_SIZE(tp, tsize); assert(size <= PY_SSIZE_T_MAX); dictoffset += (Py_ssize_t)size; assert(dictoffset > 0); assert(dictoffset % SIZEOF_VOID_P == 0); } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; } } // 如果對(duì)象 obj 存在 __dict__ 字段 那么就返回 __dict__ 字段當(dāng)中名字等于 name 的對(duì)象 if (dict != NULL) { Py_INCREF(dict); res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res); Py_DECREF(dict); goto done; } Py_DECREF(dict); } // 如果類對(duì)象定義了 __get__ 函數(shù)沒(méi)有定義 __set__ 函數(shù)而且在 dict 當(dāng)中沒(méi)有找到名為 name 的對(duì)象的話 // 那么久調(diào)用類對(duì)象的 __get__ 函數(shù) if (f != NULL) { res = f(descr, obj, (PyObject *)Py_TYPE(obj)); if (res == NULL && suppress && PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); } goto done; } // 如果類對(duì)象沒(méi)有定義 __get__ 函數(shù)那么就直接將這個(gè)類對(duì)象返回 if (descr != NULL) { res = descr; descr = NULL; goto done; } if (!suppress) { PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); } done: Py_XDECREF(descr); Py_DECREF(name); return res; }
根據(jù)對(duì)上面的程序進(jìn)行分析,我們可以到得到從對(duì)象當(dāng)中獲取屬性的順序和優(yōu)先級(jí)如下所示(以 a.attr
為例子):
- 如果屬性不是類屬性,那么很簡(jiǎn)單就是直接從對(duì)象本身的
__dict__
當(dāng)中獲取這個(gè)對(duì)象。 - 如果屬性是類屬性,如果同時(shí)定義了
__get__
和__set__
函數(shù),那么就會(huì)調(diào)用這個(gè)類對(duì)象的__get__
函數(shù),將這個(gè)函數(shù)的返回值作為a.attr
的返回值。 - 如果屬性是類屬性,如果只定義了
__get__
函數(shù),那么就會(huì)從對(duì)象a
本身的__dict__
當(dāng)中獲取attr
,如果attr
存在與a.__dict__
當(dāng)中,那么久返回這個(gè)結(jié)果,如果不存在的話那么就會(huì)調(diào)用__get__
函數(shù),將這個(gè)函數(shù)的返回值作為a.attr
的結(jié)果,如果連__get__
都沒(méi)有定義,那么就會(huì)直接返回這個(gè)類對(duì)象。
上面的函數(shù)過(guò)程用 python 語(yǔ)言來(lái)描述的話如下所示:
def find_name_in_mro(cls, name, default): "Emulate _PyType_Lookup() in Objects/typeobject.c" for base in cls.__mro__: if name in vars(base): return vars(base)[name] return default def object_getattribute(obj, name): "Emulate PyObject_GenericGetAttr() in Objects/object.c" null = object() objtype = type(obj) cls_var = find_name_in_mro(objtype, name, null) descr_get = getattr(type(cls_var), '__get__', null) if descr_get is not null: if (hasattr(type(cls_var), '__set__') or hasattr(type(cls_var), '__delete__')): return descr_get(cls_var, obj, objtype) # data descriptor if hasattr(obj, '__dict__') and name in vars(obj): return vars(obj)[name] # instance variable if descr_get is not null: return descr_get(cls_var, obj, objtype) # non-data descriptor if cls_var is not null: return cls_var # class variable raise AttributeError(name)
總結(jié)
在本篇文章當(dāng)中主要給大家深入分析了在 cpython 的內(nèi)部對(duì)于描述器的實(shí)現(xiàn)原理,其中最重要的就是在獲取屬性的時(shí)候的優(yōu)先級(jí)了。我們直接從 c 代碼的層面分析了整個(gè)獲取屬性的優(yōu)先級(jí),并且給出了 python 層面的代碼幫助大家理解。
以上就是python虛擬機(jī)之描述器實(shí)現(xiàn)原理與源碼分析的詳細(xì)內(nèi)容,更多關(guān)于python描述器實(shí)現(xiàn)原理與源碼的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python日期時(shí)間對(duì)象轉(zhuǎn)換為字符串的實(shí)例
今天小編就為大家分享一篇Python日期時(shí)間對(duì)象轉(zhuǎn)換為字符串的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-06-06pytorch實(shí)現(xiàn)Tensor變量之間的轉(zhuǎn)換
今天小編就為大家分享一篇pytorch實(shí)現(xiàn)Tensor變量之間的轉(zhuǎn)換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02更新pip3與pyttsx3文字語(yǔ)音轉(zhuǎn)換的實(shí)現(xiàn)方法
今天小編就為大家分享一篇更新pip3與pyttsx3文字語(yǔ)音轉(zhuǎn)換的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08