Python中節省記憶體的方法之二:弱引用weakref
弱引用和引用計數息息相關,在介紹弱引用之前首先簡單介紹一下引用計數。
引用計數
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>]
總結
總的來說弱引用有兩個優點:
- 不佔用引用計數,可節省記憶體
- 在原始物件被銷燬時可以回撥弱引用註冊的回撥函式
第一點在文中多處有強調,關於第二點是比較有特點的一個特性。可以使用第二點完成觀察者模式,通知一些依賴某一個物件的所有物件。