Heim  >  Artikel  >  Backend-Entwicklung  >  So verwenden Sie schwache Referenzen in Python

So verwenden Sie schwache Referenzen in Python

PHPz
PHPznach vorne
2023-05-12 23:52:111402Durchsuche

Hintergrund

Bevor wir mit der Diskussion schwacher Referenzen (weakref) beginnen, werfen wir zunächst einen Blick darauf, was eine schwache Referenz ist. Was genau macht es?

Angenommen, wir haben ein Multithread-Programm, das Anwendungsdaten gleichzeitig verarbeitet:

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

Anwendungsdaten Daten werden durch einen Schlüssel eindeutig identifiziert, und mehrere Threads können gleichzeitig auf dieselben Daten zugreifen. Da Daten viele Systemressourcen erfordern, sind die Kosten für die Erstellung und Nutzung hoch. Wir hoffen, dass Data nur eine Kopie im Programm verwaltet und diese nicht wiederholt erstellen möchte, selbst wenn mehrere Threads gleichzeitig darauf zugreifen.

Zu diesem Zweck versuchen wir, einen Caching-Middleware-Cacher zu entwerfen:

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 verwendet intern ein dict-Objekt, um die erstellte Datenkopie zwischenzuspeichern, und stellt eine Get-Methode zum Abrufen von Anwendungsdaten bereit. Wenn die Get-Methode Daten abruft, überprüft sie zunächst das Cache-Wörterbuch. Wenn die Daten bereits vorhanden sind, werden sie direkt zurückgegeben. Wenn die Daten nicht vorhanden sind, wird eines erstellt und im Wörterbuch gespeichert. Daher werden die Daten nach der ersten Erstellung in das Cache-Wörterbuch eingegeben. Wenn später andere Threads gleichzeitig darauf zugreifen, wird dieselbe Kopie im Cache verwendet.

Fühlt sich sehr gut an! Aber der Wermutstropfen ist: Bei Cacher besteht die Gefahr eines Ressourcenverlusts!

Denn sobald Daten erstellt wurden, werden sie im Cache-Wörterbuch gespeichert und niemals veröffentlicht! Mit anderen Worten: Die Ressourcen des Programms, wie zum Beispiel der Speicher, werden weiter wachsen und möglicherweise explodieren. Daher hoffen wir, dass ein Datenelement automatisch freigegeben werden kann, nachdem alle Threads nicht mehr darauf zugreifen.

Wir können die Anzahl der Datenreferenzen im Cacher verwalten und die Get-Methode akkumuliert diese Anzahl automatisch. Gleichzeitig wird eine neue Remove-Methode zum Freigeben von Daten bereitgestellt. Sie verringert zunächst die Anzahl der Referenzen und löscht die Daten aus dem Cache-Feld, wenn die Anzahl der Referenzen auf Null sinkt.

Der Thread ruft die Get-Methode auf, um die Daten abzurufen. Nachdem die Daten aufgebraucht sind, muss die Remove-Methode aufgerufen werden, um sie freizugeben. Cacher entspricht der Implementierung der Referenzzählmethode selbst, was zu mühsam ist! Verfügt Python nicht über einen integrierten Garbage-Collection-Mechanismus? Warum muss die Anwendung es selbst implementieren?

Der Hauptproblempunkt des Konflikts liegt im Cache-Wörterbuch von Cacher: Als Middleware verwendet es selbst keine Datenobjekte und sollte daher theoretisch keine Verweise auf Daten haben. Gibt es also eine schwarze Technologie, die das Zielobjekt finden kann, ohne eine Referenz zu generieren? Wir wissen, dass Aufgaben Referenzen generieren!

Typische Verwendung

Zu diesem Zeitpunkt hat schwache Referenz (schwachref) einen großen Auftritt! Eine schwache Referenz ist ein spezielles Objekt, das dem Zielobjekt zugeordnet werden kann, ohne dass eine Referenz generiert wird.

# 创建一个数据
>>> 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()

So verwenden Sie schwache Referenzen in Python

Auf diese Weise müssen wir nur das Cacher-Cache-Wörterbuch ändern, um schwache Referenzen zu speichern, und das Problem wird gelöst!

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

Da das Cache-Wörterbuch nur schwache Referenzen auf das Datenobjekt speichert, hat der Cacher keinen Einfluss auf die Referenzanzahl des Datenobjekts. Wenn alle Threads die Daten nicht mehr verwenden, sinkt der Referenzzähler auf Null und wird freigegeben.

Tatsächlich werden Wörterbücher häufig zum Zwischenspeichern von Datenobjekten verwendet. Aus diesem Grund stellt das Weakref-Modul auch zwei Wörterbuchobjekte bereit, die nur schwache Referenzen speichern:

  • weakref.WeakKeyDictionary, der Schlüssel speichert nur die Zuordnungsklasse von schwachen Referenzen (sobald der Schlüssel keine starke Referenz mehr hat, verschwindet der Schlüssel-Wert-Paar-Eintrag automatisch);

  • Daher kann unser Daten-Cache-Wörterbuch mit schwachref.WeakValueDictionary implementiert werden, und seine Schnittstelle ist genau die gleiche wie ein gewöhnliches Wörterbuch. Auf diese Weise müssen wir schwache Referenzobjekte nicht mehr selbst verwalten und die Codelogik ist prägnanter und klarer: Das

    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-Modul verfügt auch über viele nützliche Toolklassen und Toolfunktionen. Weitere Informationen finden Sie in der offiziellen Dokumentation Details und werden hier nicht wiederholt.

Wie es funktioniert

Wer genau ist also eine schwache Referenz und warum hat sie so magische Kraft? Als nächstes nehmen wir seinen Schleier ab und sehen sein wahres Aussehen!

>>> 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>

Nach den vorherigen Kapiteln sind wir bereits mit dem Lesen des Quellcodes integrierter Objekte vertraut. Die relevanten Quellcodedateien lauten wie folgt:

Include/weakrefobject.h Die Header-Datei enthält die Objektstruktur und einige Makrodefinitionen;

  • Objects/weakrefobject Die .c-Quelldatei enthält schwache Referenztypobjekte und ihre Methodendefinitionen.

  • Schauen wir uns zunächst die Feldstruktur des schwachen Referenzobjekts an, die in Zeile 10 definiert ist -41 in der Header-Datei Include/weakrefobject.h:

    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
  • Es ​​ist ersichtlich, dass die PyWeakReference-Struktur der physische Körper des schwachen Referenzobjekts ist. Es handelt sich um ein Objekt mit fester Länge. Zusätzlich zum festen Header gibt es 5 Felder:

So verwenden Sie schwache Referenzen in Pythonwr_object, Objektzeiger, der auf das referenzierte Objekt zeigt, schwache Referenz kann das referenzierte Objekt basierend auf diesem Feld finden, wird aber nicht generiert Referenz;

  • wr_callback, zeigt auf ein aufrufbares Objekt, das aufgerufen wird, wenn das referenzierte Objekt zerstört wird

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

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

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

So verwenden Sie schwache Referenzen in Python

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

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

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

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


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

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

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

实现细节

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

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

So verwenden Sie schwache Referenzen in 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 行)。

So verwenden Sie schwache Referenzen in 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 行);


    • Wenn der Rückruf leer ist, fügen Sie ihn direkt am Anfang der verknüpften Liste ein, um die spätere Wiederverwendung zu erleichtern (siehe Punkt 4); # 🎜🎜#

      Wenn der Rückruf nicht leer ist, fügen Sie ihn nach dem grundlegenden schwachen Referenzobjekt (falls vorhanden) ein, um sicherzustellen, dass sich die grundlegende schwache Referenz für einen einfachen Zugriff an der Spitze der verknüpften Liste befindet.
    • # 🎜🎜#

      Wenn ein Objekt recycelt wird, ruft die Funktion tp_dealloc die Funktion PyObject_ClearWeakRefs auf, um seine schwachen Referenzen zu bereinigen. Diese Funktion entnimmt die schwache Referenzliste des Objekts, durchläuft sie dann einzeln, bereinigt das Feld wr_object und führt die Rückruffunktion wr_callback aus (falls vorhanden). Die spezifischen Details werden nicht erweitert. Wenn Sie interessiert sind, können Sie den Quellcode in Objects/weakrefobject.c in Zeile 880 überprüfen.

    • Okay, nachdem wir diesen Abschnitt studiert haben, beherrschen wir das Wissen über schwache Referenzen gründlich. Schwache Referenzen können das Zielobjekt verwalten, ohne einen Referenzzähler zu generieren, und werden häufig in Frameworks und Middleware verwendet. Schwache Referenzen sehen magisch aus, aber tatsächlich ist das Gestaltungsprinzip ein sehr einfaches Beobachtermuster. Nachdem das schwache Referenzobjekt erstellt wurde, wird es in eine verknüpfte Liste eingefügt, die vom Zielobjekt verwaltet wird, und das Zerstörungsereignis des Objekts wird beobachtet (abonniert).

Das obige ist der detaillierte Inhalt vonSo verwenden Sie schwache Referenzen in Python. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen