弱引用和引用計數息息相關,在介紹弱引用之前首先簡單介紹一下引用計數。
引用計數
Python語言有垃圾自動回收機制,所謂垃圾就是沒有被引用的對象。垃圾回收主要使用引用計數來標記清除。
引用計數
:python中變量名和數據之間使用引用來建立聯繫。如a = [1,2,3]
。列表[1,2,3]被變量a所引用,所以列表[1,2,3]的引用計數就是1。python中每一個對象都有引用計數。
可以通過sys
模塊的getrefcount
獲取某一個對象被引用計數的個數
>>> a = [1,2,3]
>>>
>>> import sys
>>> sys.getrefcount(a) # 由於方法本身也引用了變量a,所以個數爲2。
2
>>>
>>> b = a
>>>
>>> sys.getrefcount(a)
3
>>>
b=a相當於新創建一個變量b指向[1,2,3]
垃圾回收
:當某一個對象的引用計數等於0時就表明該對象沒有被任何變量所引用,也就成爲了內存垃圾,可以被垃圾回收機制所處理。
引用計數的特點:
- 當對象引用計數等於0時可以被回收;
- 當對象應用計數不等於0時不能被回收,除非觸發手動回收;
弱引用
以上就是python中引用的基本知識,今天介紹的主角weakref
(弱引用)就是和引用的機制非常密切的模塊。弱引用就是不產生引用計數的特殊引用。
特性
:
弱引用不會增加對象的引用數量。如果將引用的目標對象稱爲 指向對象(referent)。因此,弱引用不會妨礙所指對象被當作垃圾回收。
python中的弱引用會獲取引用對象的地址,即可以調用對象對其進行相關操作,但是不會使引用的對象的引用計數增加,當引用對象的引用計數爲0時,對象還是會被回收,弱引用也無法繼續調用對象
- 弱應用可以操作指向對象的屬性
- 弱應用不會增加指向對象的引用計數個數
適合場景
:
結論:弱引用在緩存應用中很有用
有這樣一個場景,如果一個緩存的字典中保存了key爲id,value爲某大型對象這樣的鍵值對。當大型對象被刪除del object
之後,字典中保存的鍵值對依然不會被刪除。因爲字典存在,大型對象的引用計數會增加1。由於大型對象一直被引用,內存不能釋放。
使用弱引用字典來保存如上的鍵值對,當大型對象刪除時,緩存字典中的鍵值對也會被刪除。能夠有效釋放內存。
weakref的使用
weakref.ref()使用
ref 的定義
class weakref.ref(object[, callback])
ref是用來構建弱引用最常見的函數,返回對對象的弱引用。
根據原始對象是否存活,返回值不同:
- 如果原始對象仍然存活,則可以通過調用引用對象來獲得原始對象;
- 如果引用的原始對象不再存在,則調用引用對象將得到 None 。
- 支持傳入回調函數,在原始對象即將終結時將調用回調。弱引用對象將作爲回調的唯一參數傳遞
demo
import sys
import weakref
class Demo():
def __init__(self, value):
self.a = value
def get_value(self):
print(self.a)
demo = Demo(100)
demo_weakref = weakref.ref(demo) # 創建弱引用對象
print(demo_weakref()) # 通過調用函數的方法來調用,返回的是原始對象
>>> <__main__.Demo object at 0x7f21840e2e80>
print(demo_weakref() is demo) # 可以看出弱應用對象指向原始對象
>>> True
demo_weakref().get_value() # 調用原始對象的方法
>>> 100
del demo
print(demo_weakref()) # 刪除原始對象之後,弱引用對象返回None
>>> None
弱應用對象不增加引用計數
import sys
import weakref
class Demo():
def __init__(self, value):
self.a = value
def get_value(self):
print(self.a)
demo = Demo(100)
print(sys.getrefcount(demo))
>>> 2
demo_weakref = weakref.ref(demo)
print(sys.getrefcount(demo))
>>> 2
註冊函數的使用
通過ref構建弱引用對象時,可以傳入回調函數,在原始對象銷銷燬時回調函數被調用。
需要注意回調函數的參數一定要傳入弱引用對象
import sys
import weakref
class Demo():
def __init__(self, value):
self.a = value
def get_value(self):
print(self.a)
def notify_by_delete(weakref_obj):
print(f"{weakref_obj}注意:引用的對象被刪除了")
demo = Demo(100)
demo_weakref = weakref.ref(demo, notify_by_delete)
demo_weakref().get_value()
del demo
weakref.proxy 的使用
ref在使用時需要顯示調用才能獲得原始對象,使用proxy返回原始對象的代理,使用代理對象可直接訪問原始對象。
定義
weakref.proxy(object[, callback])
demo
import sys
import weakref
class Demo():
def __init__(self, value):
self.a = value
def get_value(self):
print(self.a)
demo = Demo(100)
demo_weakref = weakref.proxy(demo)
demo.get_value()
>>> 100
demo_weakref.get_value()
>>> 100
proxy相比ref省去了函數調用這一步,可以說使用更加方便。
WeakKeyDictionary
WeakKeyDictionary 是以弱引用對象爲key的字典。
優點
:創建一個key爲弱引用的字典。優點是當key不在有引用計數時,key-value的映射會在字典中消失。
定義
weakref.WeakKeyDictionary([dict])
普通字典
在普通字典中,如果key是一個變量名,那麼當變量被刪除之後,字典中的key不會被刪除。所以在一些場景中,如果是以對象作爲key,那麼刪除對象之後需要字典中的key-value也能被刪除,就可以使用弱引用對象。
import sys
import weakref
class Demo():
def __init__(self, value):
self.a = value
def get_value(self):
print(self.a)
Dict = {}
a = 100
Dict[a] = "一百"
print(Dict)
>>> {100: '一百'}
del a
print(Dict)
>>> {100: '一百'}
當變量a被刪除之後,字典Dict中的key並不受影響。
弱引用字典 demo
wkdict = weakref.WeakKeyDictionary()
demo = Demo(200)
wkdict[demo] = "二百"
print(list(wkdict.items()))
del demo
print(list(wkdict.items()))
>>>
[(<__main__.Demo object at 0x10458b3a0>, '二百')]
[]
在弱引用字典中,key是一個對象,如果對象被刪除之後,弱引用字典中的key-value鍵值對也會被刪除。
以弱引用對象爲value的字典 WeakValueDictionary
使用弱引用作爲value的映射類:當不再有對value的強引用時,將丟棄字典中的條目。與weakref.WeakKeyDictionary功能類似,只不過作爲弱引用的是value。功能類似,不再話下。
weakref.finalize
finalize 主要用來標誌原始對象的銷燬。finalize構建時傳入一個函數,當原始對象被刪除時會自動調用這個函數。
在原始對象被刪除之前也可以手動調用finalize對象,但是其最多可以被調用一次,再次調用不會調用註冊函數。
import sys
import weakref
class Demo():
def __init__(self, value):
self.a = value
def get_value(self):
print(self.a)
demo = Demo(100)
def delete_exec(x):
print('demo 被回收了..', "傳入參數:", x)
res = weakref.finalize(demo, delete_exec, 200)
del demo
獲取弱引用統計
想要獲取一個對象的弱引用情況,可以通過getweakrefcount
獲取弱引用個數,getweakrefs
獲取弱引用的列表。
import sys
import weakref
class Demo():
def __init__(self, value):
self.a = value
def get_value(self):
print(self.a)
demo = Demo(100)
demo_weakref_one = weakref.ref(demo)
demo_weakref_two = weakref.proxy(demo)
count = weakref.getweakrefcount(demo)
print(count)
>>> 2
weak_list = weakref.getweakrefs(demo)
print(weak_list)
>>> [<weakref at 0x7f2e27c1d458; to 'Demo' at 0x7f2e27be0e80>, <weakproxy at 0x7f2e27b652c8 to Demo at 0x7f2e27be0e80>]
總結
總的來說弱引用有兩個優點:
- 不佔用引用計數,可節省內存
- 在原始對象被銷燬時可以回調弱引用註冊的回調函數
第一點在文中多處有強調,關於第二點是比較有特點的一個特性。可以使用第二點完成觀察者模式,通知一些依賴某一個對象的所有對象。