python虛擬機之描述器實現(xiàn)原理與源碼分析
從字節(jié)碼角度看描述器
在前面的內(nèi)容當中我們已經(jīng)詳細分析了描述器的使用和其相關的應用,我們通常使用描述器都是將其作為類的一個類屬性使用,而使用的方式就是 a.attr,而這個使用方式使用的字節(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
>>>可以看到的是真正調用的字節(jié)碼是 LOAD_ATTR,因此只需要我們深入 LOAD_ATTR 指令我們就能夠了解這其中所有發(fā)生的內(nèi)容,了解魔法背后的神秘。
描述器源碼分析
cpython 虛擬機當中執(zhí)行這個字節(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 對應上面的代碼當中的 a 對象,name 對應上面的字符串 attr 。從上面的代碼分析我們可以知道真正獲取屬性的函數(shù)為 PyObject_GetAttr ,這個函數(shù)的源程序如下所示:
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
// 首先獲取對象 v 的類型 ,對應上面的代碼的話就是找到對象 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;
}
// 獲取對象的 tp_getattro 函數(shù) 這個函數(shù)就是負責屬性查找的函數(shù) 我們一般使用的這個屬性查找函數(shù)都是
// object 這個基類的屬性查找函數(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;
}在上面的代碼當中我們提到了 object 這個基類,因為我們需要找到他的屬性查找函數(shù),因此我們看一下這個基類在 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 */
// 這個就是真正的屬性查找函數(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.
*/
// 首先獲取對象的類型 針對于上面的源代碼來說就是找到對象 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;
}
// 這個是從所有的基類當中找到一個名字為 name 的對象 如果沒有就返回 NULL
// 這里的過程還是比較復雜 需要從類的 mro 序列當中進行查找
descr = _PyType_Lookup(tp, name);
f = NULL;
// 如果找到的類對象不為空 也就是在類本身或者基類當中找到一個名為 name 的對象
if (descr != NULL) {
Py_INCREF(descr);
// 得到類對象的 __get__ 函數(shù)
f = descr->ob_type->tp_descr_get;
// 如果對象有 __get__ 函數(shù)則進行進一步判斷
if (f != NULL && PyDescr_IsData(descr)) { // PyDescr_IsData(descr) 這個宏是查看對象是否有 __set__ 函數(shù)
// 如果是類對象又有 __get__ 函數(shù) 又有 __set__ 函數(shù) 則直接調用對象的 __get__ 函數(shù) 并且將結果返回
// 這里需要注意一下優(yōu)先級 這個優(yōu)先級是最高的 如果一個類對象定義了 __set__ 和 __get__ 函數(shù),那么
// 就會直接調用類對象的 __get__ 函數(shù)并且將這個函數(shù)的返回值返回
res = f(descr, obj, (PyObject *)obj->ob_type);
if (res == NULL && suppress &&
PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
}
goto done;
}
}
// 如果沒有名為 name 的類對象 或者雖然有名為 name 的對象 但是只要沒有同時定義 __get__ 和 __set__ 函數(shù)就需要
// 繼續(xù)往下執(zhí)行 從對象本省的 dict 當中尋找
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
// 這部分代碼就是從對象 obj 當中找到對象的 __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;
}
}
// 如果對象 obj 存在 __dict__ 字段 那么就返回 __dict__ 字段當中名字等于 name 的對象
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);
}
// 如果類對象定義了 __get__ 函數(shù)沒有定義 __set__ 函數(shù)而且在 dict 當中沒有找到名為 name 的對象的話
// 那么久調用類對象的 __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;
}
// 如果類對象沒有定義 __get__ 函數(shù)那么就直接將這個類對象返回
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ù)對上面的程序進行分析,我們可以到得到從對象當中獲取屬性的順序和優(yōu)先級如下所示(以 a.attr 為例子):
- 如果屬性不是類屬性,那么很簡單就是直接從對象本身的
__dict__當中獲取這個對象。 - 如果屬性是類屬性,如果同時定義了
__get__和__set__函數(shù),那么就會調用這個類對象的__get__函數(shù),將這個函數(shù)的返回值作為a.attr的返回值。 - 如果屬性是類屬性,如果只定義了
__get__函數(shù),那么就會從對象a本身的__dict__當中獲取attr,如果attr存在與a.__dict__當中,那么久返回這個結果,如果不存在的話那么就會調用__get__函數(shù),將這個函數(shù)的返回值作為a.attr的結果,如果連__get__都沒有定義,那么就會直接返回這個類對象。
上面的函數(shù)過程用 python 語言來描述的話如下所示:
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)總結
在本篇文章當中主要給大家深入分析了在 cpython 的內(nèi)部對于描述器的實現(xiàn)原理,其中最重要的就是在獲取屬性的時候的優(yōu)先級了。我們直接從 c 代碼的層面分析了整個獲取屬性的優(yōu)先級,并且給出了 python 層面的代碼幫助大家理解。
以上就是python虛擬機之描述器實現(xiàn)原理與源碼分析的詳細內(nèi)容,更多關于python描述器實現(xiàn)原理與源碼的資料請關注腳本之家其它相關文章!
相關文章
更新pip3與pyttsx3文字語音轉換的實現(xiàn)方法
今天小編就為大家分享一篇更新pip3與pyttsx3文字語音轉換的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08

