亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

python虛擬機之描述器實現(xiàn)原理與源碼分析

 更新時間:2023年05月09日 08:43:09   作者:一無是處的研究僧  
在本篇文章當中主要給大家介紹描述器背后的實現(xiàn)原理,通過分析?cpython對應的源代碼了解與描述器相關的字節(jié)碼的指令,我們就可以真正了解到描述器背后的原理,需要的朋友可以參考下

從字節(jié)碼角度看描述器

在前面的內容當中我們已經詳細分析了描述器的使用和其相關的應用,我們通常使用描述器都是將其作為類的一個類屬性使用,而使用的方式就是 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ā)生的內容,了解魔法背后的神秘。

描述器源碼分析

cpython 虛擬機當中執(zhí)行這個字節(jié)碼的內容如下:

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 。從上面的代碼分析我們可以知道真正獲取屬性的函數為 PyObject_GetAttr ,這個函數的源程序如下所示:

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 函數 這個函數就是負責屬性查找的函數 我們一般使用的這個屬性查找函數都是
    // object 這個基類的屬性查找函數
    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 這個基類,因為我們需要找到他的屬性查找函數,因此我們看一下這個基類在 cpython 內部的定義,在 cpython 內部 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 */
    // 這個就是真正的屬性查找函數
    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 定義可以看到真正的查找函數為 PyObject_GenericGetAttr 其函數內容如下所示:
PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
    return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0);
}

_PyObject_GenericGetAttrWithDict 函數定義如下所示:

/* 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__ 函數
        f = descr->ob_type->tp_descr_get;
        // 如果對象有 __get__ 函數則進行進一步判斷
        if (f != NULL && PyDescr_IsData(descr)) { // PyDescr_IsData(descr) 這個宏是查看對象是否有 __set__ 函數
            // 如果是類對象又有 __get__ 函數 又有 __set__ 函數 則直接調用對象的 __get__ 函數 并且將結果返回
            // 這里需要注意一下優(yōu)先級 這個優(yōu)先級是最高的 如果一個類對象定義了 __set__ 和 __get__ 函數,那么
            // 就會直接調用類對象的 __get__ 函數并且將這個函數的返回值返回
            res = f(descr, obj, (PyObject *)obj->ob_type);
            if (res == NULL && suppress &&
                    PyErr_ExceptionMatches(PyExc_AttributeError)) {
                PyErr_Clear();
            }
            goto done;
        }
    }
    // 如果沒有名為 name 的類對象 或者雖然有名為 name 的對象 但是只要沒有同時定義 __get__ 和 __set__ 函數就需要
    // 繼續(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__ 函數沒有定義 __set__ 函數而且在 dict 當中沒有找到名為 name 的對象的話
    // 那么久調用類對象的 __get__ 函數
    if (f != NULL) {
        res = f(descr, obj, (PyObject *)Py_TYPE(obj));
        if (res == NULL && suppress &&
                PyErr_ExceptionMatches(PyExc_AttributeError)) {
            PyErr_Clear();
        }
        goto done;
    }
    // 如果類對象沒有定義 __get__ 函數那么就直接將這個類對象返回
    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;
}

根據對上面的程序進行分析,我們可以到得到從對象當中獲取屬性的順序和優(yōu)先級如下所示(以 a.attr 為例子):

  • 如果屬性不是類屬性,那么很簡單就是直接從對象本身的 __dict__ 當中獲取這個對象。
  • 如果屬性是類屬性,如果同時定義了 __get____set__ 函數,那么就會調用這個類對象的 __get__ 函數,將這個函數的返回值作為 a.attr 的返回值。
  • 如果屬性是類屬性,如果只定義了 __get__ 函數,那么就會從對象 a 本身的 __dict__ 當中獲取 attr ,如果 attr 存在與 a.__dict__ 當中,那么久返回這個結果,如果不存在的話那么就會調用 __get__ 函數,將這個函數的返回值作為 a.attr 的結果,如果連 __get__ 都沒有定義,那么就會直接返回這個類對象。

上面的函數過程用 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 的內部對于描述器的實現(xiàn)原理,其中最重要的就是在獲取屬性的時候的優(yōu)先級了。我們直接從 c 代碼的層面分析了整個獲取屬性的優(yōu)先級,并且給出了 python 層面的代碼幫助大家理解。

以上就是python虛擬機之描述器實現(xiàn)原理與源碼分析的詳細內容,更多關于python描述器實現(xiàn)原理與源碼的資料請關注腳本之家其它相關文章!

相關文章

  • Tkinter組件Entry的具體使用

    Tkinter組件Entry的具體使用

    本文主要介紹了Tkinter組件Entry的具體使用,Entry組件通常用于獲取用戶的輸入文本,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Python日期時間對象轉換為字符串的實例

    Python日期時間對象轉換為字符串的實例

    今天小編就為大家分享一篇Python日期時間對象轉換為字符串的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-06-06
  • python實現(xiàn)二叉排序樹

    python實現(xiàn)二叉排序樹

    這篇文章主要介紹了python實現(xiàn)二叉排序樹,
    2022-01-01
  • Python?OpenGL基本配置方式

    Python?OpenGL基本配置方式

    這篇文章主要介紹了Python?OpenGL基本配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • Python基礎之輸入,輸出與高階賦值詳解

    Python基礎之輸入,輸出與高階賦值詳解

    這篇文章主要為大家介紹了Python基礎之輸入,輸出與高階賦值,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-11-11
  • pytorch實現(xiàn)Tensor變量之間的轉換

    pytorch實現(xiàn)Tensor變量之間的轉換

    今天小編就為大家分享一篇pytorch實現(xiàn)Tensor變量之間的轉換,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-02-02
  • 更新pip3與pyttsx3文字語音轉換的實現(xiàn)方法

    更新pip3與pyttsx3文字語音轉換的實現(xiàn)方法

    今天小編就為大家分享一篇更新pip3與pyttsx3文字語音轉換的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • python實現(xiàn)超級瑪麗游戲

    python實現(xiàn)超級瑪麗游戲

    這篇文章主要為大家詳細介紹了python實現(xiàn)超級瑪麗游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • 使用python在本地電腦上快速處理數據

    使用python在本地電腦上快速處理數據

    這篇文章主要介紹了使用python在本地電腦上快速處理數據的相關資料,需要的朋友可以參考下
    2017-06-06
  • 詳解PyQt5中Thread多線程的使用

    詳解PyQt5中Thread多線程的使用

    QThread是Qt線程類中最核心的底層類,這篇文章主要為大家介紹了PyQt5中Thread多線程的具體使用,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-08-08

最新評論