溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python對(duì)象的生命周期源碼分析

發(fā)布時(shí)間:2022-05-18 09:17:46 來源:億速云 閱讀:102 作者:zzz 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“Python對(duì)象的生命周期源碼分析”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

    思考:

    當(dāng)我們輸入這個(gè)語句的時(shí)候,Python內(nèi)部是如何去創(chuàng)建這個(gè)對(duì)象的?

    a = 1.0

    對(duì)象使用完畢,銷毀的時(shí)機(jī)又是怎么確定的呢?

    下面,我們以一個(gè)基本類型float為例,來分析對(duì)象從創(chuàng)建到銷毀這整個(gè)生命周期中的行為。

    1 C API

    Python是用C寫的,對(duì)外提供了API,讓用戶可以從C環(huán)境中與其交互,并且Python內(nèi)部也大量使用了這些API。C API分為兩類:泛型API以及特型API。

    泛型API:與類型無關(guān),屬于抽象對(duì)象層,這類API的參數(shù)是PyObject *,即可以處理任意類型的對(duì)象。以PyObject_Print為例:

    // 打印浮點(diǎn)對(duì)象
    PyObject *fo = PyFloat_FromDouble(3.14);
    PyObject_Print(fo, stdout, 0);
    // 打印整數(shù)對(duì)象
    PyObject *lo = PyLong_FromLong(100);
    PyObject_Print(lo, stdout, 0);

    特型API:與類型相關(guān),屬于具體對(duì)象層,這類API只能作用于某種類型的對(duì)象

    2 對(duì)象的創(chuàng)建

    2.1 兩種創(chuàng)建對(duì)象的方式

    Python內(nèi)部一般通過兩種方法創(chuàng)建對(duì)象:

    通過C API,多用于內(nèi)建類型

    以浮點(diǎn)類型為例,Python內(nèi)部提供PyFloat_FromDouble,這是一個(gè)特型C API,在這個(gè)接口內(nèi)部為PyFloatObject結(jié)構(gòu)體變量分配內(nèi)存,并初始化相關(guān)字段:

    PyObject *
    PyFloat_FromDouble(double fval)
    {
        PyFloatObject *op = free_list;
        if (op != NULL) {
            free_list = (PyFloatObject *) Py_TYPE(op);
            numfree--;
        } else {
            op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
            if (!op)
                return PyErr_NoMemory();
        }
        /* Inline PyObject_New */
        (void)PyObject_INIT(op, &PyFloat_Type);
        op->ob_fval = fval;
        return (PyObject *) op;
    }

    通過類型對(duì)象,多用于自定義類型

    對(duì)于自定義類型,Python就無法事先提供C API了,這種情況下就只能通過類型對(duì)象中包含的元數(shù)據(jù)(分配多少內(nèi)存,如何初始化等等)來創(chuàng)建實(shí)例對(duì)象。

    由類型對(duì)象創(chuàng)建實(shí)例對(duì)象是一個(gè)更通用的流程,對(duì)于內(nèi)建類型,除了通過C API來創(chuàng)建對(duì)象意外,同樣也可以通過類型對(duì)象來創(chuàng)建。以浮點(diǎn)類型為例,我們通過類型對(duì)象float,創(chuàng)建了一個(gè)實(shí)例對(duì)象f:

    f: float = float('3.123')

    2.2 由類型對(duì)象創(chuàng)建實(shí)例對(duì)象

    思考:既然我們可以通過類型對(duì)象來創(chuàng)建實(shí)例對(duì)象,那么類型對(duì)象中應(yīng)該存在相應(yīng)的接口。

    在PyType_Type中找到了tp_call字段:

    PyTypeObject PyType_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "type",                                     /* tp_name */
        sizeof(PyHeapTypeObject),                   /* tp_basicsize */
        sizeof(PyMemberDef),                        /* tp_itemsize */
        (destructor)type_dealloc,                   /* tp_dealloc */
        // ...
        (ternaryfunc)type_call,                     /* tp_call */
        // ...
    };

    因此,float(‘3.123’)在C層面就等價(jià)于:

    PyFloat_Type.ob_type.tp_call(&PyFloat_Type, args. kwargs)

    這里大家可以思考下為什么是PyFloat_Type.ob_type——因?yàn)槲覀冊(cè)趂loat(‘3.14’)中是通過float這個(gè)類型對(duì)象去創(chuàng)建一個(gè)浮點(diǎn)對(duì)象,而對(duì)象的通用方法是由它對(duì)應(yīng)的類型管理的,自然float的類型就是type,所以我們要找的就是type的tp_call字段。

    type_call函數(shù)的C源碼:(只列出部分)

    static PyObject *
    type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        PyObject *obj;
        // ...
        obj = type->tp_new(type, args, kwds);
        obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
        if (obj == NULL)
            return NULL;
        // ...
        type = Py_TYPE(obj);
        if (type->tp_init != NULL) {
            int res = type->tp_init(obj, args, kwds);
            if (res < 0) {
                assert(PyErr_Occurred());
                Py_DECREF(obj);
                obj = NULL;
            }
            else {
                assert(!PyErr_Occurred());
            }
        }
        return obj;
    }

    其中有兩個(gè)關(guān)鍵的步驟:(這兩個(gè)步驟大家應(yīng)該是很熟悉的)

    • 調(diào)用類型對(duì)象的tp_new函數(shù)指針,用于申請(qǐng)內(nèi)存;

    • 如果類型對(duì)象的tp_init函數(shù)指針不為空,則會(huì)對(duì)對(duì)象進(jìn)行初始化。

    總結(jié):(以float為例)

    • 調(diào)用float,Python最終執(zhí)行的是其類型對(duì)象type的tp_call指針指向的type_call函數(shù)。

    • type_call函數(shù)調(diào)用float的tp_new函數(shù)為實(shí)例對(duì)象分配內(nèi)存空間。

    • type_call函數(shù)必要時(shí)進(jìn)一步調(diào)用tp_init函數(shù)對(duì)實(shí)例對(duì)象進(jìn)行初始化。

    圖示如下:

    Python對(duì)象的生命周期源碼分析

    3 對(duì)象的多態(tài)性

    通過類型對(duì)象創(chuàng)建實(shí)例對(duì)象,最后會(huì)落實(shí)到調(diào)用type_call函數(shù),其中保存具體對(duì)象時(shí),使用的是PyObject *obj,并沒有通過一個(gè)具體的對(duì)象(例如PyFloatObject)來保存。這樣做的好處是:可以實(shí)現(xiàn)更抽象的上層邏輯,而不用關(guān)心對(duì)象的實(shí)際類型和實(shí)現(xiàn)細(xì)節(jié)。(記得當(dāng)初從C語言的面向過程向Java中的面向?qū)ο筮^度的時(shí)候,應(yīng)該就是從結(jié)構(gòu)體)

    以對(duì)象哈希值計(jì)算為例,有這樣一個(gè)函數(shù)接口:

    Py_hash_t
    PyObject_Hash(PyObject *v)
    {
        // ...
    }

    對(duì)于浮點(diǎn)數(shù)對(duì)象和整數(shù)對(duì)象:

    PyObject *fo = PyFloatObject_FromDouble(3.14);
    PyObject_Hash(fo);
    PyObject *lo = PyLongObject_FromLong(100);
    PyObject_Hash(lo);

    可以看到,對(duì)于浮點(diǎn)數(shù)對(duì)象和整數(shù)對(duì)象,我們計(jì)算對(duì)象的哈希值時(shí),調(diào)用的都是PyObject_Hash()這個(gè)函數(shù),但是對(duì)象類型不同,其行為是有區(qū)別的,哈希值計(jì)算也是如此。

    那么在PyObject_Hash函數(shù)內(nèi)部是如何區(qū)分的呢?

    PyObject_Hash()函數(shù)具體邏輯:

    Py_hash_t
    PyObject_Hash(PyObject *v)
    {
        PyTypeObject *tp = Py_TYPE(v);
        if (tp->tp_hash != NULL)
            return (*tp->tp_hash)(v);
        /* To keep to the general practice that inheriting
         * solely from object in C code should work without
         * an explicit call to PyType_Ready, we implicitly call
         * PyType_Ready here and then check the tp_hash slot again
         */
        if (tp->tp_dict == NULL) {
            if (PyType_Ready(tp) < 0)
                return -1;
            if (tp->tp_hash != NULL)
                return (*tp->tp_hash)(v);
        }
        /* Otherwise, the object can't be hashed */
        return PyObject_HashNotImplemented(v);
    }

    函數(shù)會(huì)首先通過Py_TYPE找到對(duì)象的類型,然后通過類型對(duì)象的tp_hash函數(shù)指針來調(diào)用對(duì)應(yīng)的哈希計(jì)算函數(shù)。

    即:PyObject_Hash()函數(shù)根據(jù)對(duì)象的類型,調(diào)用不同的函數(shù)版本,這就是多態(tài)。

    4 對(duì)象的行為

    除了tp_hash字段,PyTypeObject結(jié)構(gòu)體還定義了很多函數(shù)指針,這些指針最終都會(huì)指向某個(gè)函數(shù),或者為空。我們可以把這些函數(shù)指針看作是類型對(duì)象中定義的操作,這些操作決定了對(duì)應(yīng)的實(shí)例對(duì)象在運(yùn)行時(shí)的行為。

    雖然不同的類型對(duì)象中保存了對(duì)應(yīng)實(shí)例對(duì)象共有的行為,但是不同類型的對(duì)象也會(huì)存在一些共性。例如:整數(shù)對(duì)象和浮點(diǎn)數(shù)對(duì)象都支持加減乘除等擦歐總,元組對(duì)象和列表對(duì)象都支持下標(biāo)操作。因此,我們以行為為分類標(biāo)準(zhǔn),對(duì)對(duì)象進(jìn)行分類:

    Python對(duì)象的生命周期源碼分析

    Python以此為依據(jù),為每個(gè)類別都定義了一個(gè)標(biāo)準(zhǔn)操作集:

    • PyNumberMethods結(jié)構(gòu)體定義了數(shù)值型操作

    • PySequenceMethods結(jié)構(gòu)體定義了序列型操作

    • PyMappingMethods結(jié)構(gòu)體定義了關(guān)聯(lián)型操作

    如果類型對(duì)象提供了相關(guān)的操作集,則對(duì)應(yīng)的實(shí)例對(duì)象就具備對(duì)應(yīng)的行為:

    typedef struct _typeobject {
        PyObject_VAR_HEAD
        const char *tp_name; /* For printing, in format "<module>.<name>" */
        Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
       // ...
        PyNumberMethods *tp_as_number;
        PySequenceMethods *tp_as_sequence;
        PyMappingMethods *tp_as_mapping;
        // ...
    } PyTypeObject;

    以float為例,類型對(duì)象PyFloat_Type的這三個(gè)字段是這樣初始化的:

    PyTypeObject PyFloat_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "float",
        sizeof(PyFloatObject),
        // ...
        &float_as_number,                           /* tp_as_number */
        0,                                          /* tp_as_sequence */
        0,                                          /* tp_as_mapping */
        // ...
    };

    可以看到,只有tp_as_number非空,即float對(duì)象支持?jǐn)?shù)值型操作,不支持序列型操作和關(guān)聯(lián)型操作。

    5 引用計(jì)數(shù)

    在Python中,很多場(chǎng)景都涉及引用計(jì)數(shù)的調(diào)整:

    • 變量賦值

    • 函數(shù)參數(shù)傳遞

    • 屬性操作

    • 容器操作

    “Python對(duì)象的生命周期源碼分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI