Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Bagaimana untuk menggunakan rujukan lemah dalam Python

Bagaimana untuk menggunakan rujukan lemah dalam Python

PHPz
PHPzke hadapan
2023-05-12 23:52:111382semak imbas

Latar Belakang

Sebelum kita mula membincangkan rujukan yang lemah ( weakref ), mari kita lihat dahulu apakah rujukan yang lemah? Apa sebenarnya yang dilakukannya?

Andaikan kita mempunyai atur cara berbilang benang yang memproses data aplikasi secara serentak:

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

Data aplikasi Data dikenal pasti secara unik oleh kunci dan data yang sama boleh diakses oleh berbilang rangkaian pada masa yang sama masa. Memandangkan Data memerlukan banyak sumber sistem, kos penciptaan dan penggunaan adalah tinggi. Kami berharap Data hanya mengekalkan satu salinan dalam program, dan tidak mahu menciptanya berulang kali walaupun ia diakses oleh berbilang rangkaian pada masa yang sama.

Untuk tujuan ini, kami cuba mereka bentuk Cacher middleware caching:

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 secara dalaman menggunakan objek dict untuk cache salinan Data yang dibuat, dan menyediakan kaedah dapatkan untuk mendapatkan Data data aplikasi. Apabila kaedah dapatkan memperoleh data, ia mula-mula menyemak kamus cache Jika data sudah wujud, ia dikembalikan secara langsung jika data tidak wujud, ia mencipta satu dan menyimpannya dalam kamus. Oleh itu, data dimasukkan ke dalam kamus cache selepas ia mula-mula dibuat Jika urutan lain mengaksesnya pada masa yang sama kemudian, salinan yang sama dalam cache akan digunakan.

Rasa sangat baik! Tetapi lalat dalam salap ialah: Cacher mempunyai risiko kebocoran sumber!

Oleh kerana setelah Data dibuat, ia disimpan dalam kamus cache dan tidak akan dikeluarkan! Dalam erti kata lain, sumber program, seperti ingatan, akan terus berkembang dan akhirnya boleh meletup. Oleh itu, kami berharap bahawa sekeping data boleh dikeluarkan secara automatik selepas semua rangkaian tidak lagi mengaksesnya.

Kami boleh mengekalkan bilangan rujukan data dalam Cacher, dan kaedah dapatkan secara automatik mengumpulkan kiraan ini. Pada masa yang sama, kaedah alih keluar baharu disediakan untuk mengeluarkan data. Ia mula-mula mengurangkan bilangan rujukan dan memadamkan data daripada medan cache apabila bilangan rujukan menurun kepada sifar.

Benang memanggil kaedah dapatkan untuk mendapatkan data Selepas data habis, kaedah alih keluar perlu dipanggil untuk melepaskannya. Cacher adalah bersamaan dengan melaksanakan kaedah pengiraan rujukan itu sendiri, yang terlalu menyusahkan! Adakah Python tidak mempunyai mekanisme pengumpulan sampah terbina dalam? Mengapakah aplikasi itu perlu melaksanakannya sendiri?

Inti utama konflik terletak pada kamus cache Cacher: sebagai perisian tengah, ia tidak menggunakan objek data itu sendiri, jadi secara teorinya ia tidak sepatutnya mempunyai rujukan kepada data. Adakah terdapat teknologi hitam yang boleh mencari objek sasaran tanpa menghasilkan rujukan? Kami tahu bahawa tugasan menjana rujukan!

Penggunaan biasa

Pada masa ini, Rujukan lemah ( weakref ) membuat penampilan hebat! Rujukan yang lemah ialah objek khas yang boleh dikaitkan dengan objek sasaran tanpa menghasilkan rujukan.

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

Bagaimana untuk menggunakan rujukan lemah dalam Python

Dengan cara ini, kita hanya perlu menukar kamus cache Cacher untuk menyimpan rujukan yang lemah, dan masalah akan diselesaikan!

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

Memandangkan kamus cache hanya menyimpan rujukan yang lemah kepada objek Data, Cacher tidak akan menjejaskan kiraan rujukan objek Data. Apabila semua urutan telah selesai menggunakan data, kiraan rujukan turun kepada sifar dan dikeluarkan.

Malah, adalah sangat biasa untuk menggunakan kamus untuk cache objek data Atas sebab ini, modul weakref juga menyediakan dua objek kamus yang hanya menyimpan rujukan yang lemah:

  • weakref. WeakKeyDictionary , kunci hanya menyimpan kelas pemetaan rujukan lemah (apabila kunci tidak lagi mempunyai rujukan yang kuat, entri pasangan nilai kunci akan hilang secara automatik); .WeakValueDictionary , nilai hanya memegang rujukan lemah kelas Pemetaan (apabila nilai tidak lagi mempunyai rujukan yang kukuh, entri pasangan nilai kunci akan hilang secara automatik); kamus boleh dilaksanakan menggunakan weakref.WeakValueDictionary, antara mukanya Ia betul-betul sama seperti kamus biasa. Dengan cara ini, kita tidak lagi perlu mengekalkan objek rujukan yang lemah dengan diri kita sendiri, dan logik kod adalah lebih ringkas dan jelas:

    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

    modul weakref juga mempunyai banyak kelas alat dan fungsi alat yang berguna. Sila rujuk kepada rasmi dokumentasi untuk butiran khusus, yang tidak akan diterangkan di sini.
  • Bagaimana ia berfungsi

    Jadi, apakah sebenarnya rujukan yang lemah, dan mengapa ia mempunyai kuasa ajaib seperti itu? Seterusnya, mari menanggalkan tudungnya dan lihat rupa sebenar!
  • >>> 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>
Selepas bab sebelumnya, kami sudah biasa membaca kod sumber objek terbina dalam Fail kod sumber yang berkaitan adalah seperti berikut:

Sertakan/. Fail pengepala weakrefobject.h mengandungi struktur objek dan beberapa definisi makro;

Mari kita lihat dahulu Struktur medan objek rujukan lemah ditakrifkan dalam baris 10-41 fail pengepala 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

Dapat dilihat bahawa PyWeakReference struktur ialah badan objek rujukan yang lemah. Ia ialah objek dengan panjang tetap. Selain pengepala tetap, terdapat 5 medan:

  • wr_object, penunjuk objek, menunjuk kepada yang dirujuk objek, lemah Rujukan boleh mencari objek yang dirujuk berdasarkan medan ini, tetapi tiada rujukan akan dijanakan; dimusnahkan;
  • hash ,缓存被引用对象的哈希值;

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

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

Bagaimana untuk menggunakan rujukan lemah dalam Python

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

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

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

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


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

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

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

实现细节

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

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

Bagaimana untuk menggunakan rujukan lemah dalam 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 行)。

Bagaimana untuk menggunakan rujukan lemah dalam 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 行);


    • Jika panggilan balik kosong, masukkannya terus ke hadapan senarai terpaut untuk memudahkan penggunaan semula berikutnya (lihat titik 4); Jika panggilan balik tidak kosong, masukkannya selepas objek rujukan lemah asas (jika ada) untuk memastikan rujukan lemah asas berada di kepala senarai terpaut untuk akses mudah

    • Apabila objek dikitar semula, fungsi tp_dealloc akan memanggil fungsi PyObject_ClearWeakRefs untuk membersihkan rujukannya yang lemah. Fungsi ini mengeluarkan senarai rujukan objek yang lemah, kemudian melintasinya satu demi satu, membersihkan medan wr_object dan melaksanakan fungsi panggil balik wr_callback (jika ada). Butiran khusus tidak akan dikembangkan Jika anda berminat, anda boleh menyemak kod sumber dalam Objects/weakrefobject.c, yang terletak di baris 880.
    Baiklah, setelah mempelajari bahagian ini, kami telah menguasai sepenuhnya ilmu berkaitan rujukan yang lemah. Rujukan yang lemah boleh mengurus objek sasaran tanpa menghasilkan kiraan rujukan, dan sering digunakan dalam rangka kerja dan perisian tengah. Rujukan yang lemah kelihatan ajaib, tetapi sebenarnya prinsip reka bentuk adalah corak pemerhati yang sangat mudah. Selepas objek rujukan yang lemah dicipta, ia dimasukkan ke dalam senarai terpaut yang dikekalkan oleh objek sasaran, dan peristiwa pemusnahan objek diperhatikan (dilanggan).

Atas ialah kandungan terperinci Bagaimana untuk menggunakan rujukan lemah dalam Python. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam