国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

一個 Python 對象會在何時被銷毀?

開發 前端
如果對象沒了,占用的內存也要釋放的話,那么頻繁申請、釋放內存空間會使 Python 的執行效率大打折扣,更何況 Python 已經背負了人們對其執行效率的不滿這么多年。

楔子

如果對編程語言進行分類的話,一般可以分為靜態語言和動態語言,也可以分為編譯型語言和解釋型語言。但個人覺得還可以有一種劃分標準,就是是否自帶垃圾回收。關于有沒有垃圾回收,陳儒老師在《Python 2.5源碼剖析》中,總結得非常好。

對于像 C 和 C++ 這類語言,程序員被賦予了極大的自由,可以任意地申請內存。但權力的另一面對應著責任,程序員最后不使用的時候,必須負責將申請的內存釋放掉,并把無效指針設置為空。可以說,這一點是萬惡之源,大量內存泄漏、懸空指針、越界訪問的 bug 由此產生。

而現代的開發語言(比如 C#、Java)都帶有垃圾回收機制,將開發人員從維護內存分配和清理的繁重工作中解放出來,開發者不用再擔心內存泄漏的問題,但同時也剝奪了程序員和內存親密接觸的機會,并犧牲了一定的運行效率。不過好處就是提高了開發效率,并降低了 bug 發生的概率。

由于現在的垃圾回收機制已經非常成熟了,把對性能的影響降到了最低,因此大部分場景選擇的都是帶垃圾回收的語言。

而 Python 里面同樣具有垃圾回收,只不過它是為引用計數機制服務的。所以解釋器通過內部的引用計數和垃圾回收,代替程序員進行繁重的內存管理工作,關于垃圾回收我們后面會詳細說,先來看一下引用計數。

引用計數

Python 一切皆對象,所有對象都有一個 ob_refcnt 字段,該字段維護著對象的引用計數,從而也決定對象的存在與消亡。下面來探討一下引用計數,當然引用計數在介紹 PyObject 的時候說的很詳細了,這里再回顧一下。

但需要說明的是,比起類型對象,我們更關注實例對象的行為。引用計數也是如此,只有實例對象,我們探討引用計數才是有意義的。

因為內置的類型對象超越了引用計數規則,永遠都不會被析構,或者銷毀,因為它們在底層是被靜態定義好的。

圖片圖片

很明顯,內置的類型對象屬于永恒對象。關于永恒對象之前解釋過,指的是那些永遠不會被回收的對象,像 None、小整數對象池里面的整數、以及內置的類型對象,它們都是永恒對象。

如果對象是永恒對象,那么它的引用計數會直接被初始化為 uint32 最大值。當然,如果一個對象原本不是永恒對象,但它的引用計數之后達到了 uint32 最大值(有 2 ** 32 - 1 個變量在引用它),那么它也會被判定為永恒對象,但很明顯這只是理論情況,現實不可能出現,因為一個對象不可能有這么多的變量在引用它。

同理,我們自定義的類,雖然可以被回收,但是探討它的引用計數也是沒有價值的。我們舉個栗子:

class A:
    pass

del A

首先 del 關鍵字只能作用于變量,不可以作用于對象,比如 e = 2.71,可以 del e,但是不可以 del 2.71,這是不符合語法規則的。因為 del 的作用是刪除變量,并讓其指向對象的引用計數減 1,所以我們只能 del 變量,不可以 del 對象。

同樣的,使用 def、class 關鍵字定義完之后拿到的也是變量,比如上面代碼中的 A,只要是變量,就可以被 del。但是 del 變量只是刪除了該變量,換言之就是讓該變量無法再被使用,至于變量指向的對象是否會被回收,就看是否還有其它的變量也指向它。

總結:對象是否被回收完全由解釋器判斷它的引用計數是否為 0 所決定。

永恒對象

我們一直說對象的 ob_refcnt 字段負責維護引用計數,當然這是沒問題的。但 Python 從 3.12 開始又引入了 ob_refcnt_split 字段,也負責維護引用計數。

圖片圖片

ob_refcnt_split 是一個長度為 2、類型為 uint32 的數組,但只會用其中一個元素來維護引用計數。如果達到了 uint32 最大值,那么判定為永恒對象,相關源碼后續聊。

我們來看看永恒對象的初始化過程,以 list 類型對象為例,看看它的引用計數是怎么設置的。

// Objects/listobject.c
// 引用計數和類型由宏 PyVarObject_HEAD_INIT 負責設置
PyTypeObject PyList_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "list",
    sizeof(PyListObject),
    0,
    ...
};
    
// Include/object.h
#define PyVarObject_HEAD_INIT(type, size) \
    {                                     \
        PyObject_HEAD_INIT(type)          \
        (size)                            \
    },

#define PyObject_HEAD_INIT(type)    \
    {                               \
        { _Py_IMMORTAL_REFCNT },    \
        (type)                      \
    },
    
#define _Py_IMMORTAL_REFCNT UINT_MAX

我們看到類型對象在初始化的時候,引用計數直接被設置成了 uint32 最大值。當然啦,這并不是說有 2 ** 32 - 1 個變量在引用,而是通過將引用計數設置為 uint32 最大值,來表示這是一個不會被銷毀的永恒對象。

源碼解密引用計數的相關操作

操作引用計數無非就是將其加一或減一,至于什么時候加一、什么時候減一,在介紹 PyObject 的時候已經說的很詳細了,可以看一下。這里我們通過源碼,看看引用計數具體是怎么操作的。

在底層,解釋器會通過 Py_INCREF 和 Py_DECREF 兩個函數來增加和減少對象的引用計數,而當對象的引用計數減少到 0 后,Py_DECREF 將調用對應的析構函數來釋放該對象所占的內存和系統資源。這個析構函數由對象的類型對象中定義的函數指針來指定,也就是 tp_dealloc。

下面我們來看看底層實現,不過在介紹 Py_INCREF 和 Py_DECREF 之前,先來看幾個其它的函數,這些函數非常常見,有必要單獨說一下。

// Include/object.h

// 返回對象的引用計數,說白了就是獲取對象的 ob_refcnt 字段
// 因為該字段負責維護引用計數
static inline Py_ssize_t Py_REFCNT(PyObject *ob) {
    return ob->ob_refcnt;
}

// 設置對象的引用計數
static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) {
    // 如果對象是永恒對象,那么直接返回
    // 不會再對永恒對象的引用計數做任何設置
    if (_Py_IsImmortal(ob)) {
        return;
    }
    ob->ob_refcnt = refcnt;
}

// 返回對象的類型,獲取 ob_type 字段
static inline PyTypeObject* Py_TYPE(PyObject *ob) {
    return ob->ob_type;
}

// 設置對象的類型
static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) {
    ob->ob_type = type;
}

// 返回對象的 ob_size
static inline Py_ssize_t Py_SIZE(PyObject *ob) {
    // _PyVarObject_CAST(ob) 等價于 (PyVarObject *)(ob)
    return  _PyVarObject_CAST(ob)->ob_size;
}

// 設置對象的 ob_size
static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) {
    ob->ob_size = size;
}

這幾個函數是用來設置引用計數、類型和 ob_size 的,比較簡單,即使不看源碼也能猜出內部都做了什么。需要注意的是,這些函數在之前的 Python 源碼中都是以宏的形式存在,但在 3.12 里面變成內聯函數了,本質上沒有太大差異。

然后來看看 Py_INCREF 和 Py_DECREF,它們負責對引用計數執行加一和減一操作。

注意:這兩個函數里面存在宏判斷,我們這里只保留判斷之后的結果。

// Include/object.h

static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
{   
    // ob_refcnt_split 是長度為 2 的數組,但只會使用一個元素
    // 至于使用哪一個,則取決于字節序,是大端存儲還是小端存儲
    PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
    // 將當前引用計數加一
    PY_UINT32_T new_refcnt = cur_refcnt + 1;
    // 如果 cur_refcnt 已經達到了 uint32 最大值,那么加一之后會產生環繞,繼續從零開始
    // 所以如果 new_refcnt 為 0,證明當前對象的引用計數為 uint32 最大值
    // 那么該對象就是永恒對象,而永恒對象不會被回收,引用計數也不再做處理,因此直接返回
    if (new_refcnt == 0) {
        return;
    }
    // 否則說明不是引用計數,那么進行更新
    op->ob_refcnt_split[PY_BIG_ENDIAN] = new_refcnt;
    // 稍后解釋
    _Py_INCREF_STAT_INC();
}

這里估計有人發現了一個問題,就是當前只更新了 ob_refcnt_split,而沒有更新 ob_refcnt。原因很簡單,因為這兩個字段組成的是共同體,它們占用同一份內存。

ob_refcnt 是 int64 整數,ob_refcnt_split 是長度為 2 的 uint32 數組,它們都是 8 字節,并且占用的是同一份 8 字節的內存。所以 ob_refcnt_split 里面的兩個元素正好對應 ob_refcnt 的低 32 位和高 32 位。

因此在修改 ob_refcnt_split 的時候,同時也修改了 ob_refcnt,所以整個操作只進行了一次。并且從源碼中也可以看出,對象的引用計數不會超過 uint32 最大值,因為當達到這個值的時候會被判定為永恒對象,而永恒對象的引用計數不會再做任何操作,因為永恒對象會永遠存在。

但還是那句話,除非一開始就將引用計數設置為 uint32 最大值,讓對象成為永恒對象,否則單靠創建變量是不可能讓對象的引用計數達到這一限制的,因為不管再復雜的項目,也不會出現一個對象被 2 ** 32 - 1 個變量指向的情況,所以 uint32 是完全夠用的。

然后在函數的最后出現了一個 _Py_INCREF_STAT_INC 函數,它負責對一些全局統計信息進行更新,目前無需關注。

以上是 Py_INCREF,負責將引用計數加一,再來看看 Py_DECREF,它負責將引用計數減一。

// Include/object.h

static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
{
    // 如果對象是永恒對象,那么直接返回,因為永恒對象不會被回收
    // 它的引用計數不會再發生變化,始終保持 uint32 最大值
    if (_Py_IsImmortal(op)) {
        return;
    }
    // 更新一些全局統計信息,和 _Py_INCREF_STAT_INC 作用一樣
    _Py_DECREF_STAT_INC();
    // 重點來了,首先將 ob_refcnt 減一,然后判斷它是否等于 0
    // 如果為 0,說明對象已經不被任何變量引用了,那么應該被銷毀
    if (--op->ob_refcnt == 0) {
        // 調用 _Py_Dealloc 將對象銷毀,這個函數內部的邏輯很簡單
        // 雖然里面存在很多宏判斷,導致代碼看起來很復雜
        // 但如果只看編譯后的最終結果,那么代碼就只有下面三行
        /*
        PyTypeObject *type = Py_TYPE(op);
        destructor dealloc = type->tp_dealloc;
        (*dealloc)(op);
        */
        // 會獲取類型對象的 tp_dealloc,然后調用,銷毀實例對象
        _Py_Dealloc(op);
    }
}

以上就是 Py_INCREF 和 Py_DECREF 兩個函數的具體實現,但是它們不能接收空指針,如果希望能接收空指針,那么可以使用另外兩個函數。

圖片圖片

Py_XINCREF 和 Py_XDECREF 會額外對指針做一次判斷,如果為空則什么也不做,不為空再調用 Py_INCREF 和 Py_DECREF。

在一個對象的引用計數為 0 時,與該對象對應的析構函數就會被調用。但是要特別注意的是,我們之前說調用析構函數之后會回收對象,或者銷毀對象、刪除對象等等,意思是將這個對象從內存中抹去,但并不意味著要釋放空間。換句話說就是對象沒了,但對象占用的內存卻有可能還在。

如果對象沒了,占用的內存也要釋放的話,那么頻繁申請、釋放內存空間會使 Python 的執行效率大打折扣,更何況 Python 已經背負了人們對其執行效率的不滿這么多年。

所以 Python 底層大量采用了緩存池的技術,使用這種技術可以避免頻繁地申請和釋放內存空間。因此在析構的時候,只是將對象占用的空間歸還到緩存池中,并沒有真的釋放。

這一點,在后面剖析內置實例對象的實現中,將會看得一清二楚,因為大部分內置的實例對象都會有自己的緩存池。

小結

到此我們的基礎概念就算說完了,從下一篇文章開始就要詳細剖析內置對象的底層實現了,比如浮點數、復數、整數、布爾值、None、bytes 對象、bytearray 對象、字符串、元組、列表、字典、集合等等,所有的內置對象都會詳細地剖析一遍,看看它是如何實現的。

有了目前為止的這些基礎,我們后面就會輕松很多,先把對象、變量等概念梳理清楚,然后再來搞這些數據結構的底層實現。

責任編輯:武曉燕 來源: 古明地覺的編程教室
相關推薦

2011-04-11 09:39:55

對象實例

2024-12-13 08:02:10

PythonGenerator懶加載

2021-01-12 11:44:48

java垃圾回收

2019-07-09 14:30:31

華為鴻蒙操作系統操作系統

2011-04-15 17:07:13

Java

2010-10-19 10:44:49

李開復

2023-11-09 09:02:26

TypeScriptas const

2013-06-28 17:28:04

推送

2022-04-08 08:48:16

線上事故日志訂閱者

2024-02-04 16:14:38

線程開發

2021-05-21 07:26:15

DataSource接口數據庫

2025-08-27 00:01:55

2022-06-23 11:22:12

LinuxLinusLinux 社區

2009-09-02 18:36:46

LinuxLinux操作系統Linux開發

2021-09-13 08:41:52

職場互聯網自閉

2022-10-09 08:16:29

React前端

2024-01-25 11:41:00

Python開發前端

2022-08-29 08:28:58

JS對象數組

2025-11-07 01:43:00

2024-04-11 08:30:05

JavaScript數組函數
點贊
收藏

51CTO技術棧公眾號

91成人精品在线| 欧美国产日韩一区二区三区| 欧美一区二区三区四区视频| 97色伦亚洲国产| 亚欧无线一线二线三线区别| 91在线播放网站| 亚洲级视频在线观看免费1级| 国产亚洲一区二区三区不卡| 男女激情无遮挡| 欧美一区二区在线免费观看| 在线中文字幕视频| 欧美三级情趣内衣| 亚洲人一二三区| 国内精品二区| 国产在线高清视频| 另类调教123区| 91精品免费在线观看| 欧美亚洲免费高清在线观看| 亚洲性图自拍| 精品午夜久久福利影院 | 都市激情亚洲欧美| 国产精品美女久久久久久2018| 国模私拍一区二区三区| 黄色小视频在线观看| 丁香激情综合国产| 欧美高清激情视频| 欧美一区二区麻豆红桃视频| 亚洲国产精品久久久久秋霞不卡 | 超碰免费在线播放| 午夜精品福利视频网站| 日本熟妇人妻xxxxx| 福利视频网站一区二区三区| 老司机av福利| 日本不卡在线视频| 欧美成人高潮一二区在线看| 粉嫩aⅴ一区二区三区四区五区| 亚洲精品欧美精品| 国产ts人妖一区二区| 亚洲美女网站18| 中文字幕一区二区在线观看| 激情在线小视频| 欧美视频精品一区| 国产69久久| 日韩国产精品亚洲а∨天堂免| 午夜日韩成人影院| 国产精品电影在线观看| 一区二区久久| 成年人在线看片| 欧美日韩国产a| 久久a爱视频| 久久爱www成人| 国产综合自拍| 日韩中文字幕在线不卡| 亚洲欧美日韩国产综合在线 | 国产九色精品成人porny| 欧美日韩中文在线视频| 欧美午夜影院一区| 久久国产精品久久精品国产| 欧美精品videos| 国产精品观看| 婷婷六月天在线| 亚洲国产欧美久久| 欧美成人日本| 国产91对白在线观看九色| 免费毛片b在线观看| 国产日韩在线一区| www.久久精品| av香蕉成人| 国产精品久久精品国产| 综合久久久久久| 自由日本语热亚洲人| 成人羞羞视频免费| 亚洲国产精品天堂| 4438全国亚洲精品观看视频| 日韩videos| 日韩视频免费观看高清完整版 | 国产精品久久久久久久久免费桃花 | 精品国产第一福利网站| 日本一区免费在线观看| 亚洲成人精品在线观看| 欧美在线电影| 色视频www在线播放| 国产日韩欧美在线| 狠狠色狠色综合曰曰| 亚洲成av人电影| 国产免费永久在线观看| 国产欧美一区二区视频 | 不卡影院一区二区| 欧美国产亚洲视频| 一区二区欧美在线观看| 91麻豆国产自产在线观看亚洲| igao视频网在线视频| 成人激情综合网| 欧美欧美欧美欧美首页| 捆绑调教一区二区三区| 美女网站视频一区| 福利在线白白| 狠狠干一区二区| www.日韩av.com| 亚洲第一福利一区| 日本午夜一区二区| 第一区第二区在线| 国产一区电影| 激情五月六月婷婷| 国产成人久久久| 精品国产91洋老外米糕| 欧美激情一区在线观看| 在线日韩视频| 国产成人精品福利| 91大神在线网站| 少妇高潮喷水久久久久久久久久| 国产精品一区二区三区在线播放 | 亚洲少妇最新在线视频| 性xx色xx综合久久久xx| 精品国产鲁一鲁****| 91在线高清| 成人观看视频| 黄色成人在线免费观看| 亚洲在线视频观看| 九九热精品在线| 日本午夜在线视频| 成人黄色图片网站| 日本一区二区动态图| 久久精品一本| 美女久久99| jizz性欧美2| 日本黄色免费在线| 在线观看黄色av| 国产精品丝袜高跟| 伊人色**天天综合婷婷| 色吧影院999| 欧美日韩免费一区二区三区 | 久久99国产精品尤物| 亚洲激情综合| 日韩综合精品| 国产成人一二| 高清不卡亚洲| 美女18一级毛片一品久道久久综合| 91在线播放网站| 久做在线视频免费观看| aiai在线| 丰满的护士2在线观看高清| 在线免费观看h| 亚洲天天影视| 毛片在线看片| 黄色羞羞视频在线观看| bl视频在线免费观看| 午夜视频在线看| 91视频欧美| 韩日成人影院| 精品成人18| 成人三级视频| 99亚洲视频| 豆国产96在线|亚洲| 欧美经典三级视频一区二区三区| 国产精品日产欧美久久久久| 国产精品国产三级国产三级人妇| 亚洲国产日日夜夜| 91福利视频网站| 亚洲国产成人91精品| 色婷婷av一区二区三区在线观看| 日韩在线免费观看视频| 91sa在线看| 欧美成人在线免费观看| 国产黄色片免费在线观看| 黄页在线免费看| 国产免费视频在线| 羞羞的视频在线观看| 国产精品一级在线观看| 国产精品99久久精品| 男女精品网站| 国产欧美一区二区在线| 欧美一区二视频| 久久免费视频这里只有精品| 国产在线精品一区二区三区》| 亚洲 自拍 另类小说综合图区| 午夜免费啪视频观看视频| 波多野结衣中文在线| 综合综合综合综合综合网| 麻豆国产精品一区二区三区| 一区二区三区色| 日韩网站在线观看| 久久99精品国产一区二区三区| 久久精品香蕉视频| 少妇在线看www| 在线免费观看欧美| 亚洲一区在线视频| xx视频.9999.com| 亚洲国产欧美日韩| 国模精品一区二区| 日本道不卡免费一区| 久久久午夜电影| 最好看的2019的中文字幕视频| 免费不卡亚洲欧美| 第一视频专区在线| 国产欧美亚洲精品a| 国产精品看片你懂得| 欧美日本高清视频| 免费成人午夜视频| 免费在线成人激情电影|