1. 程式人生 > 其它 >Python中節省記憶體的方法之二:弱引用weakref

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. 在原始物件被銷燬時可以回撥弱引用註冊的回撥函式

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