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時就表明該對象沒有被任何變量所引用,也就成爲了內存垃圾,可以被垃圾回收機制所處理。

引用計數的特點:

  1. 當對象引用計數等於0時可以被回收;
  2. 當對象應用計數不等於0時不能被回收,除非觸發手動回收;

弱引用

以上就是python中引用的基本知識,今天介紹的主角weakref(弱引用)就是和引用的機制非常密切的模塊。弱引用就是不產生引用計數的特殊引用。

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

python中的弱引用會獲取引用對象的地址,即可以調用對象對其進行相關操作,但是不會使引用的對象的引用計數增加,當引用對象的引用計數爲0時,對象還是會被回收,弱引用也無法繼續調用對象

  1. 弱應用可以操作指向對象的屬性
  2. 弱應用不會增加指向對象的引用計數個數

適合場景
結論:弱引用在緩存應用中很有用
有這樣一個場景,如果一個緩存的字典中保存了key爲id,value爲某大型對象這樣的鍵值對。當大型對象被刪除del object之後,字典中保存的鍵值對依然不會被刪除。因爲字典存在,大型對象的引用計數會增加1。由於大型對象一直被引用,內存不能釋放。
使用弱引用字典來保存如上的鍵值對,當大型對象刪除時,緩存字典中的鍵值對也會被刪除。能夠有效釋放內存。

weakref的使用

weakref.ref()使用

ref 的定義

class weakref.ref(object[, callback])

ref是用來構建弱引用最常見的函數,返回對對象的弱引用。
根據原始對象是否存活,返回值不同:

  1. 如果原始對象仍然存活,則可以通過調用引用對象來獲得原始對象;
  2. 如果引用的原始對象不再存在,則調用引用對象將得到 None 。
  3. 支持傳入回調函數,在原始對象即將終結時將調用回調。弱引用對象將作爲回調的唯一參數傳遞

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>]

總結

總的來說弱引用有兩個優點:

  1. 不佔用引用計數,可節省內存
  2. 在原始對象被銷燬時可以回調弱引用註冊的回調函數

第一點在文中多處有強調,關於第二點是比較有特點的一個特性。可以使用第二點完成觀察者模式,通知一些依賴某一個對象的所有對象。

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