目錄
二、del與垃圾回收機制(這裏我們只討論引用計數規則的垃圾回收機制)
四、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
當然還有一些其他的 弱引用對象,可以自己再去看看官網文檔
本文章內容主要就是講明白弱引用是什麼?是怎麼個原理