日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

萬丈高樓平地起,一切從 PyObject 開始

來源: 責(zé)編: 時間:2024-05-16 09:09:28 170觀看
導(dǎo)讀楔子在前面的文章中我們說到,面向?qū)ο罄碚撝械念惡蛯ο筮@兩個概念在 Python 內(nèi)部都是通過對象實現(xiàn)的。類是一種對象,稱為類型對象,類實例化得到的也是對象,稱為實例對象。但是對象在 Python 的底層是如何實現(xiàn)的呢?Python

楔子

f6B28資訊網(wǎng)——每日最新資訊28at.com

f6B28資訊網(wǎng)——每日最新資訊28at.com

在前面的文章中我們說到,面向?qū)ο罄碚撝械念惡蛯ο筮@兩個概念在 Python 內(nèi)部都是通過對象實現(xiàn)的。類是一種對象,稱為類型對象,類實例化得到的也是對象,稱為實例對象。f6B28資訊網(wǎng)——每日最新資訊28at.com

但是對象在 Python 的底層是如何實現(xiàn)的呢?Python 解釋器是基于 C 語言實現(xiàn)的 ,但 C 并不是一個面向?qū)ο蟮恼Z言,那么它是如何實現(xiàn) Python 的面向?qū)ο蟮哪兀?span style="display:none">f6B28資訊網(wǎng)——每日最新資訊28at.com

首先對于人的思維來說,對象是一個比較形象的概念,但對于計算機來說,對象卻是一個抽象的概念。它并不能理解這是一個整數(shù),那是一個字符串,計算機所知道的一切都是字節(jié)。f6B28資訊網(wǎng)——每日最新資訊28at.com

通常的說法是:對象是數(shù)據(jù)以及基于這些數(shù)據(jù)所能進行的操作的集合。在計算機中,一個對象實際上就是一片被分配的內(nèi)存空間,這些內(nèi)存可能是連續(xù)的,也可能是離散的。f6B28資訊網(wǎng)——每日最新資訊28at.com

而 Python 的任何對象在 C 中都對應(yīng)一個結(jié)構(gòu)體實例,在 Python 中創(chuàng)建一個對象,等價于在 C 中創(chuàng)建一個結(jié)構(gòu)體實例。所以 Python 的對象,其本質(zhì)就是 C 的 malloc 函數(shù)為結(jié)構(gòu)體實例在堆區(qū)申請的一塊內(nèi)存。f6B28資訊網(wǎng)——每日最新資訊28at.com

下面我們就來分析一下對象在 C 中是如何實現(xiàn)的。f6B28資訊網(wǎng)——每日最新資訊28at.com

f6B28資訊網(wǎng)——每日最新資訊28at.com

對象的地基:PyObject

f6B28資訊網(wǎng)——每日最新資訊28at.com

f6B28資訊網(wǎng)——每日最新資訊28at.com

Python 一切皆對象,而所有的對象都擁有一些共同的信息(也叫頭部信息),這些信息位于 PyObject 中,它是 Python 對象機制的核心,下面來看看它的定義。f6B28資訊網(wǎng)——每日最新資訊28at.com

注:我們整個系列的源碼都是 3.12 版本的。f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/pytypedefs.htypedef struct _object PyObject;

我們看到具體定義位于 struct _object 中,PyObject 只是它的別名。f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/object.hstruct _object {    _PyObject_HEAD_EXTRA    union {       Py_ssize_t ob_refcnt;       PY_UINT32_T ob_refcnt_split[2];    };    PyTypeObject *ob_type;};

注:源碼中定義的 struct _object 看起來會更復(fù)雜一些,因為里面還包含了一些宏判斷,用于適配不同的操作系統(tǒng)和編譯器。f6B28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片f6B28資訊網(wǎng)——每日最新資訊28at.com

這些宏判斷我們不需要關(guān)注,對于當(dāng)前的 64 位機器來說,等價于如下。f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/object.hstruct _object {    _PyObject_HEAD_EXTRA    union {       Py_ssize_t ob_refcnt;       PY_UINT32_T ob_refcnt_split[2];    };    PyTypeObject *ob_type;};

然后是 _PyObject_HEAD_EXTRA,它也是一個宏,定義如下。f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/object.h// 如果定義了宏 Py_TRACE_REFS#ifdef Py_TRACE_REFS// 那么 _PyObject_HEAD_EXTRA 會展開成如下兩個字段// 顯然程序中創(chuàng)建的對象會組成一個雙向鏈表#define _PyObject_HEAD_EXTRA      /    PyObject *_ob_next;           /    PyObject *_ob_prev;// 用于將 _ob_next 和 _ob_prev 初始化為空#define _PyObject_EXTRA_INIT _Py_NULL, _Py_NULL,// 否則說明沒有定義宏 Py_TRACE_REFS// 那么 _PyObject_HEAD_EXTRA 和 _PyObject_EXTRA_INIT 不會有任何作用#else#  define _PyObject_HEAD_EXTRA#  define _PyObject_EXTRA_INIT#endif

關(guān)于 PyObject 的定義,再畫一張圖總結(jié)一下。f6B28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片f6B28資訊網(wǎng)——每日最新資訊28at.com

Py_TRACE_REFS 一般只在編譯調(diào)試的時候會開啟,我們從官網(wǎng)下載的都是 Release 版本,不包含這個宏,因此這里我們也不考慮它。f6B28資訊網(wǎng)——每日最新資訊28at.com

所以 PyObject 最終就等價于下面這個樣子:f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/object.hstruct _object {    union {       Py_ssize_t ob_refcnt;       PY_UINT32_T ob_refcnt_split[2];    };    PyTypeObject *ob_type;};// Include/pytypedefs.htypedef struct _object PyObject;

當(dāng)然這兩者也可以寫在一起,即定義結(jié)構(gòu)體的同時起一個別名。f6B28資訊網(wǎng)——每日最新資訊28at.com

typedef struct _object {    union {       Py_ssize_t ob_refcnt;       PY_UINT32_T ob_refcnt_split[2];    };    PyTypeObject *ob_type;} PyObject;

方式是等價的,只不過 Python 將兩者分開了,并寫在了不同的文件中。f6B28資訊網(wǎng)——每日最新資訊28at.com

了解了 PyObject 的結(jié)構(gòu)之后,我們再來看一下它內(nèi)部的字段。f6B28資訊網(wǎng)——每日最新資訊28at.com

ob_refcnt:引用計數(shù)f6B28資訊網(wǎng)——每日最新資訊28at.com

ob_refcnt 表示對象的引用計數(shù),當(dāng)對象被引用時,ob_refcnt 會自增 1;引用解除時,ob_refcnt 會自減 1。而當(dāng)對象的引用計數(shù)為 0 時,則會被回收。f6B28資訊網(wǎng)——每日最新資訊28at.com

那么在哪些情況下,引用計數(shù)會加 1 呢?哪些情況下,引用計數(shù)會減 1 呢?f6B28資訊網(wǎng)——每日最新資訊28at.com

導(dǎo)致引用計數(shù)加 1 的情況:f6B28資訊網(wǎng)——每日最新資訊28at.com

  • 對象被創(chuàng)建:比如 name = "古明地覺",此時對象就是 "古明地覺" 這個字符串, 創(chuàng)建成功時它的引用計數(shù)為 1;
  • 變量傳遞使得對象被新的變量引用:比如 name2 = name;
  • 引用該對象的某個變量作為參數(shù)傳到一個函數(shù)或者類中:比如 func(name);
  • 引用該對象的某個變量作為元組、列表、集合等容器的元素:比如 lst = [name];

導(dǎo)致引用計數(shù)減 1 的情況:f6B28資訊網(wǎng)——每日最新資訊28at.com

  • 引用該對象的變量被顯式地銷毀:del name;
  • 引用該對象的變量指向了別的對象:name = "";
  • 引用該對象的變量離開了它的作用域,比如函數(shù)的局部變量在函數(shù)執(zhí)行完畢的時候會被刪除;
  • 引用該對象的變量所在的容器被銷毀,或者變量從容器里面被刪除;

因為變量只是一個和對象綁定的符號,接地氣一點的說法就是變量是個便利貼,貼在指定的對象上面。所以 del 變量 并不是刪除變量指向的對象,而是刪除變量本身,可以理解為將對象身上的便利貼給撕掉了,其結(jié)果就是對象的引用計數(shù)減一。f6B28資訊網(wǎng)——每日最新資訊28at.com

至于對象是否被刪除(回收)則是解釋器判斷引用計數(shù)是否為 0 決定的,為 0 就刪,不為 0 就不刪,就這么簡單。f6B28資訊網(wǎng)——每日最新資訊28at.com

然后需要強調(diào)的是,在 3.12 之前的 Python 源碼中,PyObject 是這么定義的,以 3.8 為例。f6B28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片f6B28資訊網(wǎng)——每日最新資訊28at.com

在 3.12 之前,引用計數(shù)通過一個 ob_refcnt 字段來維護,字段類型為 Py_ssize_t,它是 ssize_t 的別名,在 64 位機器上等價于 int64。因此一個對象的引用計數(shù)不能超過 int64 所表示的最大范圍。但很明顯,如果不費九牛二虎之力去寫惡意代碼,是不可能超過這個范圍的。f6B28資訊網(wǎng)——每日最新資訊28at.com

還是很好理解的,但從 3.12 開始,卻搞了個共同體(union)出來,這是為啥呢?因為 Python 從 3.12 開始引入了一個概念叫永恒對象。顧名思義,永恒對象就是那些永遠不會被回收的對象。f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/object.h#define _Py_IMMORTAL_REFCNT UINT_MAX

永恒對象的引用計數(shù)為 uint32 類型的最大值,即 2 的 32 次方減 1,像 None、-5 到 256 之間的小整數(shù),都屬于永恒對象。f6B28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片f6B28資訊網(wǎng)——每日最新資訊28at.com

共同體中的 ob_refcnt 字段的作用還和之前一樣,依舊是負責(zé)維護對象的引用計數(shù)。f6B28資訊網(wǎng)——每日最新資訊28at.com

但 ob_refcnt_split 也會維護一份引用計數(shù),它是 uint32 類型的數(shù)組,長度為 2,但只會用數(shù)組的一個元素來維護。如果發(fā)現(xiàn)對象的引用計數(shù)達到了 uint32 的最大值,那么會將對象判定為永恒對象,而永恒對象永遠不會被回收。f6B28資訊網(wǎng)——每日最新資訊28at.com

所以 ob_refcnt_split 是針對永恒對象引入的,它是一個長度為 2 的 uint32 類型的數(shù)組,大小是 8 字節(jié)。而 ob_refcnt 是 Py_ssize_t 類型,等價于 int64,大小也是 8 字節(jié)。由于這兩者組成的是共同體,所以整體大小依舊是 8 字節(jié),因此 PyObject 結(jié)構(gòu)體實例的大小和之前一樣。f6B28資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然啦,雖然引用計數(shù)是由共同體來維護,但你把它當(dāng)成普通的 Py_ssize_t 類型的字段來理解也是可以的。因為 3.12 之前只有一個 ob_refcnt,而 ob_refcnt_split 是針對永恒對象專門引入的。f6B28資訊網(wǎng)——每日最新資訊28at.com

ob_type:類型指針f6B28資訊網(wǎng)——每日最新資訊28at.com

對象是有類型的,類型對象描述實例對象的行為,而 ob_type 存儲的便是對應(yīng)類型對象的指針,所以類型對象在底層是一個 PyTypeObject 結(jié)構(gòu)體實例。f6B28資訊網(wǎng)——每日最新資訊28at.com

從這里可以看出,所有的類型對象在底層都是由同一個結(jié)構(gòu)體實例化得到的,因為 PyObject 是所有對象共有的,它們的 ob_type 指向的都是 PyTypeObject。f6B28資訊網(wǎng)——每日最新資訊28at.com

所以不同的實例對象對應(yīng)不同的結(jié)構(gòu)體,但是類型對象對應(yīng)的都是同一個結(jié)構(gòu)體。f6B28資訊網(wǎng)——每日最新資訊28at.com

以上就是 PyObject,它的定義非常簡單,就一個引用計數(shù)和一個類型對象的指針。這兩個字段的大小都是 8 字節(jié),所以一個 PyObject 結(jié)構(gòu)體實例的大小是 16 字節(jié)。f6B28資訊網(wǎng)——每日最新資訊28at.com

另外,由于 PyObject 是所有對象都具有的,換句話說就是所有對象對應(yīng)的結(jié)構(gòu)體內(nèi)部都內(nèi)嵌了 PyObject,因此你在 Python 里面看到的任何一個對象都有引用計數(shù)和類型這兩個屬性。f6B28資訊網(wǎng)——每日最新資訊28at.com

>>> num = 666  >>> sys.getrefcount(num)2>>> num.__class__<class 'int'>>>> sys.getrefcount(sys)72>>> sys.__class__<class 'module'>>>> sys.getrefcount(sys.path)2>>> sys.path.__class__<class 'list'>>>> def foo():  pass... >>> sys.getrefcount(foo)2>>> foo.__class__<class 'function'>

引用計數(shù)可以通過 sys.getrefcount 函數(shù)查看,類型可以通過 type(obj) 或者 obj.__class__ 查看。f6B28資訊網(wǎng)——每日最新資訊28at.com

f6B28資訊網(wǎng)——每日最新資訊28at.com

可變對象的地基:PyVarObject

f6B28資訊網(wǎng)——每日最新資訊28at.com

f6B28資訊網(wǎng)——每日最新資訊28at.com

PyObject 是所有對象的核心,它包含了所有對象都共有的信息,但是還有那么一個屬性雖然不是每個對象都有,但至少有一大半的對象會有,能猜到是什么嗎?f6B28資訊網(wǎng)——每日最新資訊28at.com

之前說過,對象根據(jù)所占的內(nèi)存是否固定,可以分為定長對象和變長對象,而變長對象顯然有一個長度的概念,比如字符串、列表、元組等等。即便是相同類型的實例對象,但是長度不同,所占的內(nèi)存也是不同的。f6B28資訊網(wǎng)——每日最新資訊28at.com

比如字符串內(nèi)部有多少個字符,元組、列表內(nèi)部有多少個元素,顯然這里的多少也是 Python 中很多對象的共有特征。雖然不像引用計數(shù)和類型那樣是每個對象都必有的,但也是絕大部分對象所具有的。f6B28資訊網(wǎng)——每日最新資訊28at.com

所以針對變長對象,Python 底層也提供了一個結(jié)構(gòu)體,因為 Python 里面很多都是變長對象。f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/object.htypedef struct {    PyObject ob_base;    Py_ssize_t ob_size;} PyVarObject;

我們看到 PyVarObject 實際上是 PyObject 的一個擴展,它在 PyObject 的基礎(chǔ)上提供了一個 ob_size 字段,用于記錄內(nèi)部的元素個數(shù)。比如列表,列表的 ob_size 維護的就是列表的元素個數(shù),插入一個元素,ob_size 會加 1,刪除一個元素,ob_size 會減 1。f6B28資訊網(wǎng)——每日最新資訊28at.com

因此使用 len 函數(shù)獲取列表的元素個數(shù)是一個時間復(fù)雜度為 O(1) 的操作,因為 ob_size 始終和內(nèi)部的元素個數(shù)保持一致,所以會直接返回 ob_size。f6B28資訊網(wǎng)——每日最新資訊28at.com

所有的變長對象都擁有 PyVarObject,而所有的對象都擁有 PyObject,這就使得在 Python 中,對對象的引用變得非常統(tǒng)一。我們只需要一個 PyObject * 就可以引用任意一個對象,而不需要管這個對象實際是一個什么樣的對象。f6B28資訊網(wǎng)——每日最新資訊28at.com

所以 Python 變量、以及容器內(nèi)部的元素,本質(zhì)上都是一個 PyObject *。而在操作變量的時候,也要先根據(jù) ob_type 字段判斷指向?qū)ο蟮念愋停缓笤賹ふ以搶ο缶哂械姆椒ǎ@也是 Python 效率慢的原因之一。f6B28資訊網(wǎng)——每日最新資訊28at.com

由于 PyObject 和 PyVarObject 要經(jīng)常被使用,所以底層提供了兩個宏,方便定義。f6B28資訊網(wǎng)——每日最新資訊28at.com

// Include/object.h#define PyObject_HEAD    PyObject ob_base;#define PyObject_VAR_HEAD    PyVarObject ob_base;

比如定長對象浮點數(shù),在底層對應(yīng)的結(jié)構(gòu)體為 PyFloatObject,它只需在 PyObject 的基礎(chǔ)上再加一個 double 即可。f6B28資訊網(wǎng)——每日最新資訊28at.com

typedef struct {    // 等價于 PyObject ob_base;    PyObject_HEAD    double ob_fval;} PyFloatObject;

再比如變長對象列表,在底層對應(yīng)的結(jié)構(gòu)體是 PyListObject,所以它需要在 PyVarObject 的基礎(chǔ)上再加一個指向指針數(shù)組首元素的二級指針和一個容量。f6B28資訊網(wǎng)——每日最新資訊28at.com

typedef struct {    PyObject_VAR_HEAD    PyObject **ob_item;    Py_ssize_t allocated;} PyListObject;

這上面的每一個字段都代表什么,我們之前提到過,當(dāng)然這些內(nèi)置的數(shù)據(jù)結(jié)構(gòu)后續(xù)還會單獨剖析。f6B28資訊網(wǎng)——每日最新資訊28at.com

里面的 ob_item 就是指向指針數(shù)組首元素的二級指針,而 allocated 表示已經(jīng)分配的容量,一旦添加元素的時候發(fā)現(xiàn) ob_size 自增 1 之后會大于 allocated,那么解釋器就知道數(shù)組已經(jīng)滿了(容量不夠了)。于是會申請一個長度更大的指針數(shù)組,然后將舊數(shù)組內(nèi)部的元素按照順序逐個拷貝到新數(shù)組里面去,并讓 ob_item 指向新數(shù)組的首元素,這個過程就是列表的擴容,后續(xù)在剖析列表的時候還會細說。f6B28資訊網(wǎng)——每日最新資訊28at.com

所以我們看到列表在添加元素的時候,地址是不會改變的,即使容量不夠了也沒有關(guān)系,直接讓 ob_item 指向新的數(shù)組就好了,至于 PyListObject 對象本身的地址是不會變化的。f6B28資訊網(wǎng)——每日最新資訊28at.com

f6B28資訊網(wǎng)——每日最新資訊28at.com

小結(jié)

f6B28資訊網(wǎng)——每日最新資訊28at.com

f6B28資訊網(wǎng)——每日最新資訊28at.com

PyObject 是 Python 對象的核心,因為 Python 對象在 C 的層面就是一個結(jié)構(gòu)體,并且所有的結(jié)構(gòu)體都嵌套了 PyObject 結(jié)構(gòu)體。而 PyObject 內(nèi)部有引用計數(shù)和類型這兩個字段,因此我們可以肯定的說 Python 的任何一個對象都有引用計數(shù)和類型這兩個屬性。f6B28資訊網(wǎng)——每日最新資訊28at.com

另外大部分對象都有長度的概念,所以 PyObject 再加上長度就誕生出了 PyVarObject,它在 PyObject 的基礎(chǔ)上添加了一個 ob_size 字段,用于描述對象的長度。比如字符串內(nèi)部的 ob_size 維護的是字符串的字符個數(shù),元組、列表、字典等等,其內(nèi)部的 ob_size 維護的是存儲的元素個數(shù),所以使用 len 函數(shù)獲取對象長度是一個 O(1) 的操作。f6B28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-88377-0.html萬丈高樓平地起,一切從 PyObject 開始

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 一次炫技差點引發(fā)的慘案

下一篇: 常見,但是總回答不好的面試題:JS 模塊化以及模塊打包器

標(biāo)簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 横峰县| 平邑县| 峡江县| 贵南县| 汉川市| 黔南| 通道| 阿拉善右旗| 新闻| 梁平县| 志丹县| 武定县| 武汉市| 沅江市| 富蕴县| 微山县| 稷山县| 涞水县| 云阳县| 获嘉县| 德江县| 丰县| 蒲江县| 民县| 青神县| 莱芜市| 罗甸县| 得荣县| 南宁市| 临安市| 习水县| 临江市| 宁阳县| 桂林市| 福建省| 玉环县| 江孜县| 志丹县| 辛集市| 武宣县| 板桥市|