目錄
背景
典型用法
工作原理
实现细节
首頁 後端開發 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

熱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.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

mysql 是否要付費 mysql 是否要付費 Apr 08, 2025 pm 05:36 PM

MySQL 有免費的社區版和收費的企業版。社區版可免費使用和修改,但支持有限,適合穩定性要求不高、技術能力強的應用。企業版提供全面商業支持,適合需要穩定可靠、高性能數據庫且願意為支持買單的應用。選擇版本時考慮的因素包括應用關鍵性、預算和技術技能。沒有完美的選項,只有最合適的方案,需根據具體情況謹慎選擇。

mysql安裝後怎麼使用 mysql安裝後怎麼使用 Apr 08, 2025 am 11:48 AM

文章介紹了MySQL數據庫的上手操作。首先,需安裝MySQL客戶端,如MySQLWorkbench或命令行客戶端。 1.使用mysql-uroot-p命令連接服務器,並使用root賬戶密碼登錄;2.使用CREATEDATABASE創建數據庫,USE選擇數據庫;3.使用CREATETABLE創建表,定義字段及數據類型;4.使用INSERTINTO插入數據,SELECT查詢數據,UPDATE更新數據,DELETE刪除數據。熟練掌握這些步驟,並學習處理常見問題和優化數據庫性能,才能高效使用MySQL。

如何針對高負載應用程序優化 MySQL 性能? 如何針對高負載應用程序優化 MySQL 性能? Apr 08, 2025 pm 06:03 PM

MySQL數據庫性能優化指南在資源密集型應用中,MySQL數據庫扮演著至關重要的角色,負責管理海量事務。然而,隨著應用規模的擴大,數據庫性能瓶頸往往成為製約因素。本文將探討一系列行之有效的MySQL性能優化策略,確保您的應用在高負載下依然保持高效響應。我們將結合實際案例,深入講解索引、查詢優化、數據庫設計以及緩存等關鍵技術。 1.數據庫架構設計優化合理的數據庫架構是MySQL性能優化的基石。以下是一些核心原則:選擇合適的數據類型選擇最小的、符合需求的數據類型,既能節省存儲空間,又能提升數據處理速度

HadiDB:Python 中的輕量級、可水平擴展的數據庫 HadiDB:Python 中的輕量級、可水平擴展的數據庫 Apr 08, 2025 pm 06:12 PM

HadiDB:輕量級、高水平可擴展的Python數據庫HadiDB(hadidb)是一個用Python編寫的輕量級數據庫,具備高度水平的可擴展性。安裝HadiDB使用pip安裝:pipinstallhadidb用戶管理創建用戶:createuser()方法創建一個新用戶。 authentication()方法驗證用戶身份。 fromhadidb.operationimportuseruser_obj=user("admin","admin")user_obj.

Navicat查看MongoDB數據庫密碼的方法 Navicat查看MongoDB數據庫密碼的方法 Apr 08, 2025 pm 09:39 PM

直接通過 Navicat 查看 MongoDB 密碼是不可能的,因為它以哈希值形式存儲。取回丟失密碼的方法:1. 重置密碼;2. 檢查配置文件(可能包含哈希值);3. 檢查代碼(可能硬編碼密碼)。

mysql 需要互聯網嗎 mysql 需要互聯網嗎 Apr 08, 2025 pm 02:18 PM

MySQL 可在無需網絡連接的情況下運行,進行基本的數據存儲和管理。但是,對於與其他系統交互、遠程訪問或使用高級功能(如復制和集群)的情況,則需要網絡連接。此外,安全措施(如防火牆)、性能優化(選擇合適的網絡連接)和數據備份對於連接到互聯網的 MySQL 數據庫至關重要。

mysql workbench 可以連接到 mariadb 嗎 mysql workbench 可以連接到 mariadb 嗎 Apr 08, 2025 pm 02:33 PM

MySQL Workbench 可以連接 MariaDB,前提是配置正確。首先選擇 "MariaDB" 作為連接器類型。在連接配置中,正確設置 HOST、PORT、USER、PASSWORD 和 DATABASE。測試連接時,檢查 MariaDB 服務是否啟動,用戶名和密碼是否正確,端口號是否正確,防火牆是否允許連接,以及數據庫是否存在。高級用法中,使用連接池技術優化性能。常見錯誤包括權限不足、網絡連接問題等,調試錯誤時仔細分析錯誤信息和使用調試工具。優化網絡配置可以提升性能

mysql 需要服務器嗎 mysql 需要服務器嗎 Apr 08, 2025 pm 02:12 PM

對於生產環境,通常需要一台服務器來運行 MySQL,原因包括性能、可靠性、安全性和可擴展性。服務器通常擁有更強大的硬件、冗餘配置和更嚴格的安全措施。對於小型、低負載應用,可在本地機器運行 MySQL,但需謹慎考慮資源消耗、安全風險和維護成本。如需更高的可靠性和安全性,應將 MySQL 部署到雲服務器或其他服務器上。選擇合適的服務器配置需要根據應用負載和數據量進行評估。

See all articles