python weakref的用法

目錄

前言

一、變量

1.1 變量是什麼?

1.2  ==和is之間的比較

二、del與垃圾回收機制(這裏我們只討論引用計數規則的垃圾回收機制)

三、弱引用

3.1 弱引用是什麼?

3.2 弱引用介紹與使用

四、weakref.ref() 和weakref.proxy() 的區別


 

前言

首先提一點:大家遇到python模塊的使用問題,儘可能去 python document去找答案。

但是關於weakref,官網上給的例子,並不能讓我們理解這個弱引用。
於是在網上查了一些資料,也是比較模糊。

於是我還是從變量垃圾回收再到若弱引用講起這件事吧。因爲他們是息息相關的,只有l理解了變量的引用和垃圾回收纔會 更好的 理若引用的概念。

然後最後我再舉2個例子,說明弱引用是怎麼體現出來的。

一、變量

1.1 變量是什麼?

變量是一個對象別名,可以理解成變量是貼在對象上的一個標籤,所以當執行

my_list = [1,2,3,4]

其實就是在 [1,2,3,4]這個對象上貼了一個標籤-my_list,我們可以通過這個標籤來找到對象,進而可以操作對象。

那麼我又執行:

my_list2 = my_list

這個時候發生了什麼呢,其實就是又給[1,2,3,4]這個對象添加了一個標籤。當然,這種說法是隻限於對可變對象的操作。我們可以參考下圖:

每個變量都有 標識 、類型 和 。對象一旦創建,它的標識絕不會變;可以把標識理解爲對象在內存中的地址。is 運算符比較兩個對象的標識;id() 函數返回對象標識的整數表示。

因此當我們對上面執行 id(my_list1)  與 id(my_list) 他們返回的值是一樣的,因爲他們指向同一個對象,my_list與my_list1只是標籤而已。

1.2  ==is之間的比較

== 運算符比較兩個對象的值(對象中保存的數據),而 is 比較對象的標識。

 

is 運算符比 == 速度快,因爲它不能重載.接比較兩個對象的 整數 ID。

a == b 是語法糖,等同於 a.__eq__(b)

__eq__ 方法繼承自 object, 比較兩個對象的 ID,結果與 is 一樣。但是多數內置類型使用更有意義的方式覆蓋了 __eq__ 方法,會考慮對象屬性的值。相等性測試可能涉及大量處理工作,例如,比較大型集合或嵌套層級深的結構時。

 

二、del與垃圾回收機制(這裏我們只討論引用計數規則的垃圾回收機制)

python中對象絕不會自行銷燬;然而,無法得到對象時,可能會被當作垃圾回收。無法得到對象包括兩種:①沒有人引用這個對象了,也就是說這個對象身上被貼的標籤都沒有了,這時候我們其實就找不到這個對象了;②相互引用

 

del 語句刪除名稱(也就是我們說的標籤),而不是對象。當我們把貼在對象身上的標籤全部刪除了,這時候python垃圾回收機制的引用計數(可以理解爲貼標籤計數)檢測到引用此對象的次數爲0,那麼就觸發了垃圾回收機制,銷燬此對象。

因此del命令並不會刪除對象,而是當del刪除了對象的最後一個引用時,會觸發垃圾回收機制,回收器將對象銷燬。這個概念要搞清。(看例1的代碼)

 

重新綁定也可能會導致對象的引用數量歸零,導致對象被銷燬。什麼意思呢?

我們執行下面的代碼:

my_list = [1,2,3,4]
my_list = [3,4,5,6]

這個時候,對象[1,2,3,4]就被銷燬了,爲什麼?

因爲my_list這個標籤從對象[1,2,3,4]上被撕下來了,貼到了對象[3,4,5,6]上。這時候對象[1,2,3,4]的引用計數是不是就爲0了,這時候就觸發垃圾回收機制,將[1,2,3,4]這個對象給銷燬了。

所以說,重新綁定也可能導致對象被銷燬

(可以看例2的代碼)

 

如果兩個對象相互引用(不懂相互引用的可以自行學習一下),當它們的引用只存在二者之間時,垃圾回收程序會判定它們都無法獲取,進而把它們都銷燬。

__del__ 特殊方法.不會銷燬實例,不應該在代碼中調用。即將銷燬實例時,Python 解釋器會調用 __del__ 方法,給實例最後的機會,釋放外部資源。 參考標準庫del特殊方法.

在 CPython 中,垃圾回收使用的主要算法是引用計數。

實際上,每個對象都會統計有多少引用指向自己.

當引用計數歸零時,對象立即就被銷燬:CPython 會在對象上調用 __del__ 方法(如果定義了),然後釋放分配給對象的內存。

CPython 2.0 增加了分代垃圾回收算法,用於檢測引用循環中涉及的對象組——如果一組對象之間全是相互引用,即使再出色的引用方式也會導致組中的對象不可獲取。

Python 的其他實現有更復雜的垃圾回收程序,而且不依賴引用計數,這意味着,對象的引用數量爲零時可能不會立即調用 __del__ 方法。

 

代碼示例2
>>> import weakref
>>> s1 = {1,2,3}    # 給對象{1,2,3}貼了一個標籤s1
>>> def bye():      # 對象{1,2,3}被銷燬時,調用這個函數
...     print("拜拜,你被銷燬了")
...
>>> ender = weakref.finalize(s1,bye)      # 綁定回調函數
>>> ender.alive                           # 看對象{1,2,3}仍然存活
True
>>> s1 = {4,5,6}                          # 當標籤s1從{1,2,3}上撕下來,對象{1,2,3}被銷燬了
拜拜,你被銷燬了
>>> ender.alive
False
>>>
代碼示例2
# 使用 weakref.finalize 註冊一個在銷燬對象時調用的回調函數。
In [166]: import weakref

In [167]: s1 = {1,2,3}

In [168]: s2 =  s1  # 指向同一個集合.

In [169]: def bye():  # 回調函數一定不能是要銷燬的對象的綁定方法(類方法),否則會有一個指向對象的引用。
     ...:     print("拜拜,你被銷燬了")
     ...:

In [170]: ender = weakref.finalize(s1,bye)  # 註冊回調, 並返回一個變量,判斷是否銷燬。

In [171]: ender.alive
Out[171]: True

In [172]: del s1

In [173]: ender.alive # 說明 del s1 是刪除引用,而不是對象。
Out[173]: True

In [174]: s2 = 'spam'  # 重新綁定 s2 後,表示 {1,2,3} 無法獲取 (引用計數爲0),此時調用 bye 回調函數.
拜拜,你被銷燬了

In [175]: ender.alive  # ender 爲 弱引用, 不在 計數 範圍內.
Out[175]: False

所以,每個引用就相當於一個標籤,通過這個標籤我們可以找到這個對象。一旦這些標籤別撕沒了,也就是對象的引用爲0的時候,就出觸發python的垃圾回收的機制。

 

三、弱引用

這個時候可以引出我們心心念的弱引用了。

3.1 弱引用是什麼?

在上文,我們看到,當執行 my_list=[1,2,3,4]時,這時候就相當於給對象[1,2,3,4]加了一個強引用(標籤)。再執行my_list1 = my_list時,那麼對象[1,2,3,4]的強引用個數就變爲了2,我們想要觸發python的垃圾回收機制銷燬對象[1,2,3,4]時,就必須把兩個強引用my_list和my_list1都刪除。

這時候,my_list2 = [1,2,3,4]這種方式,我不像使my_list2成爲對象的強引用,那麼我就可以把my_list2定義爲一個弱引用,這時候,就當發生貼標籤的操作時,就會是一個弱引用。而弱引用不會影響垃圾回收的計數。也就是說,一個對象,只要強引用個數爲0,就會觸發python的垃圾回收機制,而不管你有多少個弱引用,都是沒關係的。

 

3.2 弱引用介紹與使用

弱引用不會增加對象的引用數量。引用的目標對象稱爲 所指對象 (referent)。因此,弱引用不會妨礙所指對象被當作垃圾回收。

弱引用在緩存應用中很有用,因爲不想僅因爲被緩存引用着而始終保存緩存對象。

使用 weakref.ref 實例可以獲取所指對象。如果對象存在,調用弱引用可以獲取對象;否則返回 None 。

weakref.ref 類其實是低層接口,供高級用途使用,多數程序最好使用 weakref 工具集 和 finalize 。

weakref 工具集合: WeakKeyDictionary 、 WeakValueDictionary 、WeakSet 、finalize(內部使用弱引用)

 

WeakValueDictionary: 實現的是一種可變映射,裏面的值是對象的弱引用。

被引用的對象在程序中的其他地方被當作垃圾回收後,對應的鍵會自動從 WeakValueDictionary 中刪除。因此,WeakValueDictionary 經常用於緩存。

 

WeakSet: 保存元素弱引用的集合類。元素沒有強引用時,集合會把它刪除。

如果一個類需要知道所有實例,一種好的方案是創建一個 WeakSet 類型的類屬性,保存實例的引用。

如果使用常規的 set ,實例永遠不會被垃圾回收,因爲類中有實例的強引用,而類存在的時間與 Python 進程一樣長,除非顯式刪除類。

 

弱引用侷限:

  • 基本的 list 和 dict 實例不能作爲所指對象, 但是它們的子類可以作爲弱引用所指對象.

  • 基本的 int 、 list 、 tuple 、string 、dict 實例不能作爲弱引用的目標。

  • 但是 set 實例可以作爲所指對象。

  • 但是, str 、 dict 、list 的子類實例 和 用戶自定義的類型實例 可以作爲弱引用所指對象.

  • 然而, int 、 tuple 的子類實例 也不能作爲弱應用對象.

 

3.3 弱引用使用舉例

任何的數據結構都是可以弱引用的,我們要多利用weakref包中提供的工具類

# 前提: Python 控制檯會自動把 _ 變量綁定到結果不爲 None 的表達式結果上。
In [1]: import weakref

In [2]: a_set = {0,1}

In [3]: wref = weakref.ref(a_set)  # 弱引用對象 wref

In [4]: wref
Out[4]: <weakref at 0x10f29aea8; to 'set' at 0x10e906f28>

In [5]: wref()  # 返回的是被引用的對象,{0, 1}。因爲是控制檯會話,所以 {0, 1} 會綁定給 _ 變量。
Out[5]: {0, 1}

In [6]: a_set = {2, 3, 4}   # a_set 不再指代 {0, 1} 集合,因此集合的引用數量減少了。但是 _ 變量仍然指代它。

In [7]: wref()
Out[7]: {0, 1}

In [8]: wref() is None  # 計算這個表達式時,{0, 1} 存在,因此 wref() 不是 None。但是,隨後 _ 綁定到結果值 False。現在 {0, 1} 沒有強引用了。
Out[8]: False

In [9]: wref() is None  # 因爲 {0, 1} 對象不存在了,所以 wref() 返回 None。
Out[9]: Ture  

 

# WeakValueDictionary 示例:
class Cheese:

    def __init__(self, kind):
        self.kind = kind

    def __repr__(self):
        return 'Cheese(%r)' % self.kind

# 執行:
In [2]: import weakref

In [3]: stock = weakref.WeakValueDictionary()  # 創建弱引用字典實例。

In [4]: catalog = [Cheese('Read Leicester'), Cheese('Tilsit'),Cheese('Brie'), Cheese('Parmesan')]

In [6]: for cheese in catalog:
   ...:     stock[cheese.kind] = cheese  # 名稱映射到實例. [弱引用]
   ...:

In [7]: sorted(stock.keys())
Out[7]: ['Brie', 'Parmesan', 'Read Leicester', 'Tilsit']

In [8]: del catalog

In [9]: sorted(stock.keys())  # 爲什麼還剩一個? 因爲臨時變量。
Out[9]: ['Parmesan']

In [10]: del cheese

In [11]: sorted(stock.keys())  # 臨時變量刪除後,爲空.
Out[11]: []

 

下面這個例子,是我自己編寫的,大家要搞懂下面的這個例子,相信對弱引用,就有些理解了。

 

>>> import weakref
>>>
>>> class C:                            # 這裏新建一個類,因爲WeakValueDictionary()
...     def __init__(self, value):      # 要求value是一個obj
...             self.value = value
...
>>> def test_weak_value_dict():
...     d= weakref.WeakValueDictionary()
...     k1 = 'test1'                    
...     v1 = C(1)                       # 這時候C(1)是有一個強引用的:v1
...     d[k1] = v1                      # 這個語句也就是字典賦值,但是由於我們用的
...     print(d[k1])                    # WeakValueDictionary(),所以字典裏的是弱引用
...     del v1                          # 這時候刪除了C(1)唯一的強引用 v1,因此
...     print(d[k1])                    # WeakValueDictionary()裏面 k1,v1 這個鍵值對消失了
...
>>> test_weak_value_dict()
<__main__.C object at 0x000001793E545198>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in test_weak_value_dict
  File "C:\Users\ASUS\Anaconda3\lib\weakref.py", line 137, in __getitem__
    o = self.data[key]()
KeyError: 'test1'
>>>




# 我們如果使用dict代替WeakValueDictionary(),會發生什麼呢?
# dict內部的引用都是強引用,因此我們刪除了C(1)的強引用v1,還有dict裏面對C(1)的強引用,因此c(1)不會被銷燬

>>> import weakref
>>>
>>> class C:
...     def __init__(self, value):
...             self.value = value
...
>>> def test_weak_value_dict():
...     d= dict()
...     k1 = 'test1'
...     v1 = C(1)
...     d[k1] = v1
...     print(d[k1])
...     del v1
...     print(d[k1])
...
>>> test_weak_value_dict()
<__main__.C object at 0x000001793E545390>
<__main__.C object at 0x000001793E545390>


# 沒有發生報錯,就是因爲dict裏面的引用爲強引用

再看下面一個例子,直接報錯,搞明白原因了嗎?

>>> import weakref
>>>
>>> class C:
...     def __init__(self, value):
...             self.value = value
...
>>> def test_weak_value_dict():
...     d= weakref.WeakValueDictionary()
...     k1 = 'test1'
...     d[k1] = C(1)
...     print(d[k1])
...
>>> test_weak_value_dict()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in test_weak_value_dict
  File "C:\Users\ASUS\Anaconda3\lib\weakref.py", line 137, in __getitem__
    o = self.data[key]()
KeyError: 'test1'
>>>

爲什麼報錯:keyerror?

因爲這條記錄已經被刪除了。爲什麼?

我們看對C(1)的引用有誰? 只有WeakValueDictionary()中的弱引用,根本沒有強引用。

所以觸發垃圾回收機制將C(1)銷燬了,因此WeakValueDictionary()關於C(1)的記錄也就刪除了。

 

四、weakref.ref() 和weakref.proxy() 的區別

其實這兩個只是使用上有稍微的區別,proxy()算是給用戶提供一個更加簡潔的接口,看下面的代碼就懂了。

>>>
>>> import weakref
>>> class C:
...     def __init__(self, value):
...             self.value = value
...
>>> ref = weakref.ref(c_obj)
>>> ref()
<__main__.C object at 0x000001793E5454A8>
>>> ref().value
1
>>>
>>> proxy = weakref.proxy(c_obj)
>>> proxy
<weakproxy at 0x000001793DF55CC8 to C at 0x000001793E5454A8>
>>> proxy.value
1
>>>

我們在使用weak.ref時,返回值ref,需要執行ref()纔是弱引用的對象,ref() 相當於 c_obj

而weakref.proxy的返回值直接就是弱引用的對象,返回值proxy直接相當於c_obj

 

當然還有一些其他的 弱引用對象,可以自己再去看看官網文檔

本文章內容主要就是講明白弱引用是什麼?是怎麼個原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章