搜尋
首頁後端開發Python教學Python中弱引用怎麼使用

Python中弱引用怎麼使用

May 12, 2023 pm 11:52 PM
python

背景

開始討論弱引用( weakref )之前,我們先來看看什麼是弱引用?它到底有什麼作用?

假設我們有一個多執行緒程序,並發處理應用資料:

# 占用大量资源,创建销毁成本很高\
class Data:\
    def __init__(self, key):\
        pass

應用程式資料 Data 由一個 key 唯一標識,同一個資料可能被多個執行緒同時存取。由於 Data 需要佔用許多系統資源,創建和消費的成本很高。我們希望 Data 在程式中只維護一個副本,就算被多個執行緒同時訪問,也不想重複建立。

為此,我們嘗試設計一個快取中間件 Cacher :

import threading
# 数据缓存
class Cacher:
    def __init__(self):
        self.pool = {}
        self.lock = threading.Lock()
    def get(self, key):
        with self.lock:
            data = self.pool.get(key)
            if data:
                return data
            self.pool[key] = data = Data(key)
            return data

Cacher 內部用一個 dict 物件來快取已建立的 Data 副本,並提供 get 方法來取得應用資料 Data 。 get 方法取得資料時先查快取字典,如果資料已存在,便直接傳回;如果資料不存在,則建立一個並儲存到字典中。因此,資料首次被創建後就進入快取字典,後續如有其它線程同時訪問,使用的都是快取中的同一個副本。

感覺非常好!但美中不足的是:Cacher 有資源外洩的風險!

因為 Data 一旦建立後,就保存在快取字典中,永遠不會釋放!換句話說,程式的資源例如內存,會不斷地成長,最終很有可能會爆掉。因此,我們希望一個資料等所有執行緒都不再存取後,能夠自動釋放。

我們可以在 Cacher 中維護資料的引用次數, get 方法自動累積這個數。同時提供一個 remove 新方法用於釋放數據,它先自減引用次數,並在引用次數降為零時將數據從快取字段中刪除。

執行緒呼叫 get 方法取得數據,資料用完後需要呼叫 remove 方法將其釋放。 Cacher 等於自己也實作了一遍引用數數法,這也太麻煩了吧! Python 不是內建了垃圾回收機制嗎?為什麼應用程式還需要自行實作呢?

衝突的主要癥結在於 Cacher 的快取字典:它作為一個中間件,本身並不使用資料對象,因此理論上不應該對資料產生引用。那有什麼黑科技能夠在不產生引用的前提下,找到目標物嗎?我們知道,賦值都是會產生引用的!

典型用法

這時,弱引用( weakref )隆重登場了!弱引用是一種特殊的對象,能夠在不產生引用的前提下,關聯目標對象。

# 创建一个数据
>>> d = Data('fasionchan.com')
>>> d
<__main__.Data object at 0x1018571f0>

# 创建一个指向该数据的弱引用
>>> import weakref
>>> r = weakref.ref(d)

# 调用弱引用对象,即可找到指向的对象
>>> r()
<__main__.Data object at 0x1018571f0>
>>> r() is d
True

# 删除临时变量d,Data对象就没有其他引用了,它将被回收
>>> del d
# 再次调用弱引用对象,发现目标Data对象已经不在了(返回None)
>>> r()

Python中弱引用怎麼使用

這樣一來,我們只要將 Cacher 快取字典改成保存弱引用,問題就迎刃而解!

import threading
import weakref
# 数据缓存
class Cacher:
    def __init__(self):
        self.pool = {}
        self.lock = threading.Lock()
    def get(self, key):
        with self.lock:
            r = self.pool.get(key)
            if r:
                data = r()
                if data:
                    return data
            data = Data(key)
            self.pool[key] = weakref.ref(data)
            return data

由於快取字典只保存 Data 物件的弱引用,因此 Cacher 不會影響 Data 物件的參考計數。當所有執行緒都用完資料後,引用計數就降為零因而被釋放。

實際上,用字典快取資料物件的做法很常用,為此 weakref 模組也提供了兩種只保存弱引用的字典物件:

  • weakref. WeakKeyDictionary ,鍵只保存弱引用的映射類別(一旦鍵不再有強引用,鍵值對條目將自動消失);

  • weakref.WeakValueDictionary ,值只保存弱引用的映射類別(一旦值不再有強引用,鍵值對條目將自動消失);

#因此,我們的資料快取字典可以採用 weakref.WeakValueDictionary 來實現,它的接口跟普通字典完全一樣。這樣我們不用再自行維護弱引用對象,程式碼邏輯更加簡潔明了:

import threading
import weakref
# 数据缓存
class Cacher:
    def __init__(self):
        self.pool = weakref.WeakValueDictionary()
        self.lock = threading.Lock()
    def get(self, key):
        with self.lock:
            data = self.pool.get(key)
            if data:
                return data
            self.pool[key] = data = Data(key)
            return data

weakref 模組還有很多好用的工具類和工具函數,具體細節請參考官方文檔,這裡不再贅述。

工作原理

那麼,弱引用到底是何方神聖,為什麼會有如此神奇的魔力呢?接下來,我們一起揭下它的面紗,一睹真容!

>>> d = Data(&#39;fasionchan.com&#39;)

# weakref.ref 是一个内置类型对象
>>> from weakref import ref
>>> ref
<class &#39;weakref&#39;>

# 调用weakref.ref类型对象,创建了一个弱引用实例对象
>>> r = ref(d)
>>> r
<weakref at 0x1008d5b80; to &#39;Data&#39; at 0x100873d60>

經過前面章節,我們對閱讀內建物件原始碼已經輕車熟路了,相關原始碼檔案如下:

  • Include/weakrefobject.h 頭檔案包含物件結構體和一些巨集定義;

  • Objects/weakrefobject.c 來源檔案包含弱引用型別物件及其方法定義;

我們先扒一扒弱引用對象的字段結構,定義於 Include/weakrefobject.h 頭文件中的第 10-41 行:

typedef struct _PyWeakReference PyWeakReference;

/* PyWeakReference is the base struct for the Python ReferenceType, ProxyType,
 * and CallableProxyType.
 */
#ifndef Py_LIMITED_API
struct _PyWeakReference {
    PyObject_HEAD

    /* The object to which this is a weak reference, or Py_None if none.
     * Note that this is a stealth reference:  wr_object&#39;s refcount is
     * not incremented to reflect this pointer.
     */
    PyObject *wr_object;

    /* A callable to invoke when wr_object dies, or NULL if none. */
    PyObject *wr_callback;

    /* A cache for wr_object&#39;s hash code.  As usual for hashes, this is -1
     * if the hash code isn&#39;t known yet.
     */
    Py_hash_t hash;

    /* If wr_object is weakly referenced, wr_object has a doubly-linked NULL-
     * terminated list of weak references to it.  These are the list pointers.
     * If wr_object goes away, wr_object is set to Py_None, and these pointers
     * have no meaning then.
     */
    PyWeakReference *wr_prev;
    PyWeakReference *wr_next;
};
#endif

由此可見,PyWeakReference 結構體便是弱引用對象的肉身。它是定長對象,除固定頭部外還有 5 個欄位:

Python中弱引用怎麼使用

  • #wr_object ,物件指針,指向被引用對象,弱引用根據該欄位可以找到被引用對象,但不會產生引用;

  • wr_callback ,指向一個可呼叫對象,當被引用的物件銷毀時將被呼叫;

  • hash ,缓存被引用对象的哈希值;

  • wr_prev 和 wr_next 分别是前后向指针,用于将弱引用对象组织成双向链表;

结合代码中的注释,我们知道:

Python中弱引用怎麼使用

  • 弱引用对象通过 wr_object 字段关联被引用的对象,如上图虚线箭头所示;

  • 一个对象可以同时被多个弱引用对象关联,图中的 Data 实例对象被两个弱引用对象关联;

  • 所有关联同一个对象的弱引用,被组织成一个双向链表,链表头保存在被引用对象中,如上图实线箭头所示;

  • 当一个对象被销毁后,Python 将遍历它的弱引用链表,逐一处理:


    • 将 wr_object 字段设为 None ,弱引用对象再被调用将返回 None ,调用者便知道对象已经被销毁了;

    • 执行回调函数 wr_callback (如有);

由此可见,弱引用的工作原理其实就是设计模式中的 观察者模式( Observer )。当对象被销毁,它的所有弱引用对象都得到通知,并被妥善处理。

实现细节

掌握弱引用的基本原理,足以让我们将其用好。如果您对源码感兴趣,还可以再深入研究它的一些实现细节。

前面我们提到,对同一对象的所有弱引用,被组织成一个双向链表,链表头保存在对象中。由于能够创建弱引用的对象类型是多种多样的,很难由一个固定的结构体来表示。因此,Python 在类型对象中提供一个字段 tp_weaklistoffset ,记录弱引用链表头指针在实例对象中的偏移量。

Python中弱引用怎麼使用

由此一来,对于任意对象 o ,我们只需通过 ob_type 字段找到它的类型对象 t ,再根据 t 中的 tp_weaklistoffset 字段即可找到对象 o 的弱引用链表头。

Python 在 Include/objimpl.h 头文件中提供了两个宏定义:

/* Test if a type supports weak references */
#define PyType_SUPPORTS_WEAKREFS(t) ((t)->tp_weaklistoffset > 0)

#define PyObject_GET_WEAKREFS_LISTPTR(o) \
    ((PyObject **) (((char *) (o)) + Py_TYPE(o)->tp_weaklistoffset))
  • PyType_SUPPORTS_WEAKREFS 用于判断类型对象是否支持弱引用,仅当 tp_weaklistoffset 大于零才支持弱引用,内置对象 list 等都不支持弱引用;

  • PyObject_GET_WEAKREFS_LISTPTR 用于取出一个对象的弱引用链表头,它先通过 Py_TYPE 宏找到类型对象 t ,再找通过 tp_weaklistoffset 字段确定偏移量,最后与对象地址相加即可得到链表头字段的地址;

我们创建弱引用时,需要调用弱引用类型对象 weakref 并将被引用对象 d 作为参数传进去。弱引用类型对象 weakref 是所有弱引用实例对象的类型,是一个全局唯一的类型对象,定义在 Objects/weakrefobject.c 中,即:_PyWeakref_RefType(第 350 行)。

Python中弱引用怎麼使用

根据对象模型中学到的知识,Python 调用一个对象时,执行的是其类型对象中的 tp_call 函数。因此,调用弱引用类型对象 weakref 时,执行的是 weakref 的类型对象,也就是 type 的 tp_call 函数。tp_call 函数则回过头来调用 weakref 的 tp_new 和 tp_init 函数,其中 tp_new 为实例对象分配内存,而 tp_init 则负责初始化实例对象。

回到 Objects/weakrefobject.c 源文件,可以看到 PyWeakref_RefType 的 tp_new 字段被初始化成 *weakref___new_*  (第 276 行)。该函数的主要处理逻辑如下:

  • 解析参数,得到被引用的对象(第 282 行);

  • 调用 PyType_SUPPORTS_WEAKREFS 宏判断被引用的对象是否支持弱引用,不支持就抛异常(第 286 行);

  • 调用 GET_WEAKREFS_LISTPTR 行取出对象的弱引用链表头字段,为方便插入返回的是一个二级指针(第 294 行);

  • 调用 get_basic_refs 取出链表最前那个 callback 为空 基础弱引用对象(如有,第 295 行);

  • 如果 callback 为空,而且对象存在 callback 为空的基础弱引用,则复用该实例直接将其返回(第 296 行);

  • 如果不能复用,调用 tp_alloc 函数分配内存、完成字段初始化,并插到对象的弱引用链表(第 309 行);


    • 如果callback 為空,直接將其插入到鍊錶最前面,方便後續復用(請參閱第4 點);

    • 如果callback 非空,將其插到基礎弱引用物件(如有)之後,保證基礎弱引用位於鍊錶頭,方便取得;

當一個物件被回收後,tp_dealloc 函數將呼叫 PyObject_ClearWeakRefs 函數對它的弱引用進行清理。此函數取出物件的弱引用鍊錶,然後逐一遍歷,清理 wr_object 欄位並執行 wr_callback 回調函數(如有)。具體細節不再展開,有興趣的話可以自行查閱 Objects/weakrefobject.c 中的源碼,位於 880 行。

好了,經過本節學習,我們徹底掌握了弱引用相關知識。弱引用可以在不產生引用計數的前提下,對目標物件進行管理,常用於框架和中介軟體中。弱引用看起來很神奇,其實設計原理是非常簡單的觀察者模式。弱引用物件建立後便插到一個由目標物件維護的鍊錶中,觀察(訂閱)物件的銷毀事件。

以上是Python中弱引用怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:亿速云。如有侵權,請聯絡admin@php.cn刪除
Python:自動化,腳本和任務管理Python:自動化,腳本和任務管理Apr 16, 2025 am 12:14 AM

Python在自動化、腳本編寫和任務管理中表現出色。 1)自動化:通過標準庫如os、shutil實現文件備份。 2)腳本編寫:使用psutil庫監控系統資源。 3)任務管理:利用schedule庫調度任務。 Python的易用性和豐富庫支持使其在這些領域中成為首選工具。

Python和時間:充分利用您的學習時間Python和時間:充分利用您的學習時間Apr 14, 2025 am 12:02 AM

要在有限的時間內最大化學習Python的效率,可以使用Python的datetime、time和schedule模塊。 1.datetime模塊用於記錄和規劃學習時間。 2.time模塊幫助設置學習和休息時間。 3.schedule模塊自動化安排每週學習任務。

Python:遊戲,Guis等Python:遊戲,Guis等Apr 13, 2025 am 12:14 AM

Python在遊戲和GUI開發中表現出色。 1)遊戲開發使用Pygame,提供繪圖、音頻等功能,適合創建2D遊戲。 2)GUI開發可選擇Tkinter或PyQt,Tkinter簡單易用,PyQt功能豐富,適合專業開發。

Python vs.C:申請和用例Python vs.C:申請和用例Apr 12, 2025 am 12:01 AM

Python适合数据科学、Web开发和自动化任务,而C 适用于系统编程、游戏开发和嵌入式系统。Python以简洁和强大的生态系统著称,C 则以高性能和底层控制能力闻名。

2小時的Python計劃:一種現實的方法2小時的Python計劃:一種現實的方法Apr 11, 2025 am 12:04 AM

2小時內可以學會Python的基本編程概念和技能。 1.學習變量和數據類型,2.掌握控制流(條件語句和循環),3.理解函數的定義和使用,4.通過簡單示例和代碼片段快速上手Python編程。

Python:探索其主要應用程序Python:探索其主要應用程序Apr 10, 2025 am 09:41 AM

Python在web開發、數據科學、機器學習、自動化和腳本編寫等領域有廣泛應用。 1)在web開發中,Django和Flask框架簡化了開發過程。 2)數據科學和機器學習領域,NumPy、Pandas、Scikit-learn和TensorFlow庫提供了強大支持。 3)自動化和腳本編寫方面,Python適用於自動化測試和系統管理等任務。

您可以在2小時內學到多少python?您可以在2小時內學到多少python?Apr 09, 2025 pm 04:33 PM

兩小時內可以學到Python的基礎知識。 1.學習變量和數據類型,2.掌握控制結構如if語句和循環,3.了解函數的定義和使用。這些將幫助你開始編寫簡單的Python程序。

如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?Apr 02, 2025 am 07:18 AM

如何在10小時內教計算機小白編程基礎?如果你只有10個小時來教計算機小白一些編程知識,你會選擇教些什麼�...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具